Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Plugin to add issue and PR to GitHub Project #10982

Merged
merged 2 commits into from Mar 13, 2019

Conversation

taragu
Copy link
Contributor

@taragu taragu commented Jan 27, 2019

Fixes #10514
Need to wait for PR #10401 to be merged

/kind feature

TODO:

@k8s-ci-robot k8s-ci-robot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. kind/feature Categorizes issue or PR as related to a new feature. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Jan 27, 2019
@k8s-ci-robot k8s-ci-robot added area/prow Issues or PRs related to prow sig/testing Categorizes an issue or PR as relevant to SIG Testing. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jan 27, 2019
Copy link
Contributor

@stevekuznetsov stevekuznetsov left a comment

Choose a reason for hiding this comment

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

Great start here! I think there's still some opportunity to hash out the user experience (/cc @cblecker) and GitHub token usage (/cc @cjwagner)

prow/github/client.go Show resolved Hide resolved
@@ -222,6 +222,15 @@ repo_milestone:
maintainers_id: 2460384
maintainers_team: kubernetes-milestone-maintainers

repo_project:
# Default maintainer for projects
'':
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this mean?

Copy link
Contributor

Choose a reason for hiding this comment

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

We use '*' in other plugins to denote the default. Would be good to either:

  1. match that here
  2. match the structure of the branch protection config here for hierarchical data with overrides

@cjwagner @fejta please let me know which direction you'd rather see.

Copy link
Contributor Author

@taragu taragu Feb 13, 2019

Choose a reason for hiding this comment

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

@stevekuznetsov for approach 2, does something like this work?

project:
  - orgs:
    - kubernetes:
      # Org level config, which can be overriden by repo level configs under repos
      maintainers_team_id: 2460384
      maintainers_team_name: kubernetes-milestone-maintainers
      columns:
        - To do
        - To triage
        - Backlog
      - repos:
        - kubernetes:
          maintainers_team_id: 2460384
          maintainers_team_name: kubernetes-milestone-maintainers
          columns:
            - To do
            - To triage
            - Backlog
    - kubeflow:
      maintainers_team_id: 2460384
      maintainers_team_name: kubernetes-milestone-maintainers
      columns:
        - To do
        - To triage
        - Backlog

Copy link
Contributor Author

Choose a reason for hiding this comment

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

or something like this? i think this might be more readable:

project:
  # Org level config, which can be overriden by repo level configs under repos
  kubernetes/*:
  - maintainer_team_id: 2460384
    maintainers_team_name: kubernetes-milestone-maintainers
    columns:
    - To do
    - To triage
    - Backlog
  kubernetes/kubernetes:
  - maintainer_team_id: 2460384
    maintainers_team_name: kubernetes-milestone-maintainers
    columns:
    - To do
    - To triage
    - Backlog

Copy link
Contributor

Choose a reason for hiding this comment

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

I think your first mockup is more true to the branchprotection config and I would rather see that one, but don't let me be the only opinion here!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stevekuznetsov I see. I prefer the second one because it's easier to read IMHO

Copy link
Contributor

Choose a reason for hiding this comment

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

Totally. @fejta thoughts? Another data point is (unfortunately) we have momentum behind the approach that already exists in our config, and keeping things similar would help everyone keep it straight

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stevekuznetsov yeah I see. It makes sense to keep it consistent with existing configs

Label *Label `json:"label,omitempty"`
Lgtm []Lgtm `json:"lgtm,omitempty"`
RepoMilestone map[string]Milestone `json:"repo_milestone,omitempty"`
ProjectConfigMap map[string]ProjectConfig `json:"repo_project,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Mapped by org/repo?

@@ -372,6 +373,16 @@ type Milestone struct {
MaintainersTeam string `json:"maintainers_team,omitempty"`
}

// ProjectConfig contains the configuration options for the project plugin
type ProjectConfig struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a good config to emulate the branch-protection-style tree overrides?

Usage: "/project <board>, /project <board> <column>, or /project clear <board>",
Description: "Updates the project board and column for an issue or PR",
Featured: false,
WhoCanUse: "Members of the project maintainers GitHub team can use the '/project' command.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this team be called out here with the config value?

Copy link
Contributor

Choose a reason for hiding this comment

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

Outstanding

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stevekuznetsov i'm not sure what's the best way to call out the team value here, because I don't have access to the org and repo information

}

// Get all projects for this org
orgProjects, err := gc.GetOrgProjects(org)
Copy link
Contributor

Choose a reason for hiding this comment

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

We should only make this call if we don't find the project we need in the repo

projectMap := buildProjectMap(projects)
projectID, ok := projectMap[proposedProject]

if proposedProject == clearKeyword {
Copy link
Contributor

Choose a reason for hiding this comment

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

If this is the case, we shouldn't load project columns from GitHub

projectToClear := matches[2]
projectToClearID, ok := projectMap[projectToClear]
if ok {
existingProjectCard, err := gc.GetProjectCard(projectToClearID, e.Number)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need his call if we already have the ID?

Copy link
Contributor Author

@taragu taragu Jan 30, 2019

Choose a reason for hiding this comment

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

We have the project ID but we need to project card ID in order to move the project card

}
}

// If proposedColumn is not found (or not provided), add to the "To do",
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should expose the default column as a config option and do nothing if it is not set?

// Move this issue/PR to the new column if there's already a project card for
// this issue/PR in this project
existingProjectCard, err := gc.GetProjectCard(projectID, e.Number)
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Will you get a not found error here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah probably will, in which case I shouldn't check for err here because we will move onto creating a new project card

@fejta
Copy link
Contributor

fejta commented Jan 28, 2019

please pass tests

@taragu taragu force-pushed the add-issue-to-project branch 2 times, most recently from adcac06 to 394f49d Compare February 10, 2019 23:35
@fejta
Copy link
Contributor

fejta commented Feb 11, 2019

Dependent PR seems stalled. Asked for an ETA on it.

@stevekuznetsov
Copy link
Contributor

I think it's fine to just commit the client changes in this PR and not depend on the other.

Copy link
Contributor

@stevekuznetsov stevekuznetsov left a comment

Choose a reason for hiding this comment

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

This is looking really good! Some nits, some comments on how often we post back to GitHub

@@ -222,6 +222,15 @@ repo_milestone:
maintainers_id: 2460384
maintainers_team: kubernetes-milestone-maintainers

repo_project:
# Default maintainer for projects
'':
Copy link
Contributor

Choose a reason for hiding this comment

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

We use '*' in other plugins to denote the default. Would be good to either:

  1. match that here
  2. match the structure of the branch protection config here for hierarchical data with overrides

@cjwagner @fejta please let me know which direction you'd rather see.

ProjectMaintainersTeam string `json:"project_maintainers_team,omitempty"`

// If no column is provided when adding an issue/PR to a project, the issue/PR
// will be added to one of these columns (in the order they are specified)
Copy link
Contributor

Choose a reason for hiding this comment

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

Wait, so when does it add to the second column in this list?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stevekuznetsov if the first column in the default list (To do) doesn't exist in the project, then it'll look for the second column (To triage), and then the third Backlog

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this might be confusing or surprising behavior for users. @cblecker

Usage: "/project <board>, /project <board> <column>, or /project clear <board>",
Description: "Updates the project board and column for an issue or PR",
Featured: false,
WhoCanUse: "Members of the project maintainers GitHub team can use the '/project' command.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Outstanding

}

pluginHelp := &pluginhelp.PluginHelp{
Description: "The project plugin allows members of a configurable GitHub team to set the project and column on an issue or pull request.",
Copy link
Contributor

Choose a reason for hiding this comment

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

s/configurable //

pluginHelp := &pluginhelp.PluginHelp{
Description: "The project plugin allows members of a configurable GitHub team to set the project and column on an issue or pull request.",
Config: func(repos []string) map[string]string {
configMap := make(map[string]string)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we generally prefer

configMap := map[string]string{}

When no size is required

repoProjects, err := gc.GetRepoProjects(org, repo.Name)
if err != nil {
log.WithError(err).Errorf("Error listing all projects under repo %s", repo.Name)
return err
Copy link
Contributor

Choose a reason for hiding this comment

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

In these cases we should report some internal error to the user on GitHub and ask them to re-try their command

invalidNumArgs = "Please provide 2 or 3 arguments. Example usage: /project 0.5.0, /project 0.5.0 3999801, /project clear 0.4.0"
projectTeamMsg = "The project maintainers team is the Github team %q with ID: %d."
clearKeyword = "clear"
projectNameToIDMap map[string]int
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's not use a global here, but we would need a project client similar to that which the other plugins have like ownersClient or slack client. Alternatively if we don't think it is necessary to have an in-memory cache in this plugin for projects, we can just forget about it and offload the caching to the ghproxy. Sorry, I know I brought this up in the first place, but removing this might be the best approach for the first impl

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stevekuznetsov I see. I think it might be overkill to have a client in this case. Could you point me to an example of how to offload the caching to ghproxy?

Copy link
Contributor

Choose a reason for hiding this comment

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

Using ghproxy will be invisible to this code -- we just pass the proxy as the URL to GitHub when we start up the binary. So, to use it we can just ignore the fact in the code and keep things simple here.

if proposedProject == clearKeyword {
if err := gc.DeleteProjectCard(projectID); err != nil {
log.WithError(err).Errorf("Error removing project card from project %s (ID %d) for %s/%s#%d.", projectToFind, projectID, org, repo, e.Number)
return err
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be shown to the user?

log.WithError(err).Errorf("Error removing project card from project %s (ID %d) for %s/%s#%d.", projectToFind, projectID, org, repo, e.Number)
return err
}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we report a success to the user?

return err
}

// If proposedColumn is not found (or not provided), add to one of the default
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be documented in the configuration fields

Copy link
Contributor

Choose a reason for hiding this comment

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

@cblecker is this surprising behavior? Should we just do the simple thing and error if we can't find the column that was requested?

@stevekuznetsov
Copy link
Contributor

Really would love @kubernetes/sig-contributor-experience-pr-reviews (@cblecker) to look at this with @cjwagner -- the impl is very close, but the UX and workflow should be OK'ed first.

@k8s-ci-robot k8s-ci-robot added the sig/contributor-experience Categorizes an issue or PR as relevant to SIG Contributor Experience. label Feb 11, 2019
@k8s-ci-robot k8s-ci-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Feb 15, 2019
@taragu taragu force-pushed the add-issue-to-project branch 5 times, most recently from a976635 to d9e1dc2 Compare February 15, 2019 23:16
@taragu taragu changed the title [WIP] Plugin to add issue and PR to GitHub Project Plugin to add issue and PR to GitHub Project Feb 15, 2019
@taragu
Copy link
Contributor Author

taragu commented Mar 1, 2019

/test pull-test-infra-lint

Copy link
Contributor

@stevekuznetsov stevekuznetsov left a comment

Choose a reason for hiding this comment

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

This is looking really nice. I think soon we should merge it, turn it on for k/test-infra and test it out. That being said, it's also getting large enough to be hard to review

@cblecker @cjwagner

project_config:
project_org_configs:
kubernetes:
org_maintainers_team_id: 2460384
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: could be

org_maintainers_team:
  id:
  name:

With this, it looks like you could override the team ID but not the team name with a repo config? Is that something we want?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes that's true. When checking a user against the team only the team ID is used. I will remove the team name from the config

To do
project_repo_configs:
kubernetes:
repo_maintainers_team_id: 2460384
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to specify these? They are the same as in the owning org

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's probably a bad example for me to use the same team here. They could be different and the repo setting will override the org setting

plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider)
}

func getMaintainerTeam(pluginConfigs plugins.ProjectConfig, org string, repo string) (int, string) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: this could be a method on ProjectConfig

return 0, ""
}

func getColumnMap(pluginConfigs plugins.ProjectConfig, org string, repo string) map[string]string {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: this could be a method on ProjectConfig

return nil
}

func getOrgColumnMap(pluginConfigs plugins.ProjectConfig, org string, repo string) map[string]string {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: this could be a method on ProjectConfig and does not need the repo parameter

repos, err := gc.GetRepos(org, false)
if err != nil {
msg = fmt.Sprintf(errListingReposMsg, org)
return gc.CreateComment(org, repo, e.Number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, msg))
Copy link
Contributor

Choose a reason for hiding this comment

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

So some of these errors are useful to users -- like if they try to use this command with a project that doesn't exist, or if they're not in the group. However, this call will fail only if there is an infrastructure error -- if we don't have permissions or can't hit the API or something. We should just return the error here

// specified in the project config and see if any of them exists on the
// proposed project
if proposedColumnName == "" {
defaultColumn := ""
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar comment here, you could just call getColumnMap and not need explicit default values


// Move this issue/PR to the new column if there's already a project card for
// this issue/PR in this project
existingProjectCard, _ := gc.GetColumnProjectCard(proposedColumnID, e.Number)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we ignoring this error?

}
}

func TestParseCommand(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This could use negative test cases

if proposedProject != test.proposedProject ||
proposedColumn != test.proposedColumn ||
shouldClear != test.shouldClear {
t.Errorf("\nFor command %s, expected\n proposedProject = %s\n proposedColumn = %s\n shouldClear = %t\nbut got\n proposedProject = %s\n proposedColumn = %s\n shouldClear = %t\n", test.command, test.proposedProject, test.proposedColumn, test.shouldClear, proposedProject, proposedColumn, shouldClear)
Copy link
Contributor

Choose a reason for hiding this comment

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

Would be nicer for the person who breaks this test to check them separately and do tailored messaged to each

Copy link
Contributor

Choose a reason for hiding this comment

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

Or wrap them in a struct and use ObjectReflectDiff or something

@taragu
Copy link
Contributor Author

taragu commented Mar 3, 2019

@stevekuznetsov Just realized the project name can be multiple words, for example a command like /project Deflaking kubernetes e2e tests Inbox (unprioritized) won't be parse properly. Should the project ID (or column ID) be provided in the command instead?

@taragu taragu force-pushed the add-issue-to-project branch 2 times, most recently from 4f496e8 to 373b6b0 Compare March 6, 2019 23:08
@taragu
Copy link
Contributor Author

taragu commented Mar 6, 2019

Talked to @stevekuznetsov on slack about the multi-word case for project name. We can try out this plugin with the single-word case and then have followup discussion on the command format to handle multi-word project and column names

@stevekuznetsov
Copy link
Contributor

@cblecker would love to hear your input on the above ^^
As is this is useful, can we deploy and work on making it better? Hate to see it sit around forever in this PR

@taragu taragu force-pushed the add-issue-to-project branch 2 times, most recently from d947930 to 96ff294 Compare March 7, 2019 18:49
@k8s-ci-robot k8s-ci-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Mar 7, 2019
@k8s-ci-robot k8s-ci-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Mar 7, 2019
@stevekuznetsov
Copy link
Contributor

/lgtm
/approve

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Mar 13, 2019
@k8s-ci-robot
Copy link
Contributor

LGTM label has been added.

Git tree hash: 823cb0dd17bf83db215d7a22d83d5a1edb7d3f2c

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: stevekuznetsov, taragu

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Mar 13, 2019
@k8s-ci-robot k8s-ci-robot merged commit 56ad764 into kubernetes:master Mar 13, 2019
@k8s-ci-robot
Copy link
Contributor

@taragu: Updated the plugins configmap in namespace default using the following files:

  • key plugins.yaml using file prow/plugins.yaml

In response to this:

Fixes #10514
Need to wait for PR #10401 to be merged

/kind feature

TODO:

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. area/prow Issues or PRs related to prow cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/feature Categorizes issue or PR as related to a new feature. lgtm "Looks good to me", indicates that a PR is ready to be merged. sig/contributor-experience Categorizes an issue or PR as relevant to SIG Contributor Experience. sig/testing Categorizes an issue or PR as relevant to SIG Testing. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants