Skip to content

Commit

Permalink
Merge pull request #51 from mikegrima/prepFor1
Browse files Browse the repository at this point in the history
Fixes and features in preparation for v. 1.0.
  • Loading branch information
mikegrima committed Jul 24, 2017
2 parents 0b0a468 + 4e3d888 commit 213a03a
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 6 deletions.
104 changes: 100 additions & 4 deletions command_plugins/github/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ def __init__(self):
"help": "Get Deploy Key Public Key",
"enabled": True
},
"!SetTopics": {
"command": "!SetTopics",
"func": self.set_repo_topics_command,
"user_data_required": True,
"help": "Sets the Topics for a GitHub repo",
"enabled": True
}
}
self.token = None

Expand Down Expand Up @@ -266,8 +273,8 @@ def set_repo_homepage_command(self, data, user_data, org, repo, homepage):
validation_func=lookup_real_org, validation_func_kwargs={}),
dict(name="repo", properties=dict(type=str, help="The repository to add the outside collaborator to."),
validation_func=extract_repo_name, validation_func_kwargs={}),
dict(name="permission", properties=dict(type=str, help="The permission to grant, must be one "
"of: `{values}`"),
dict(name="permission", properties=dict(type=str.lower, help="The permission to grant, must be one "
"of: `{values}`"),
choices="permitted_permissions"),
],
optional=[]
Expand Down Expand Up @@ -323,7 +330,8 @@ def add_outside_collab_command(self, data, user_data, collab, org, repo, permiss
dict(name="org", properties=dict(type=str, help="The organization that contains the team."),
validation_func=lookup_real_org, validation_func_kwargs={}),
dict(name="team", properties=dict(type=str, help="The team to add the user to.")),
dict(name="role", properties=dict(type=str, help="The role to grant the user. Must be one of: `{values}`"),
dict(name="role", properties=dict(type=str.lower, help="The role to grant the user. "
"Must be one of: `{values}`"),
choices="permitted_roles"),
],
optional=[]
Expand Down Expand Up @@ -600,7 +608,7 @@ def set_branch_protection_command(self, data, user_data, org, repo, branch, togg
validation_func=lookup_real_org, validation_func_kwargs={}),
dict(name="repo", properties=dict(type=str, help="The name of the repo to list PRs on."),
validation_func=extract_repo_name, validation_func_kwargs={}),
dict(name="state", properties=dict(type=str, help="The state of the PR. Must be one of: `{values}`"),
dict(name="state", properties=dict(type=str.lower, help="The state of the PR. Must be one of: `{values}`"),
choices="permitted_states")
],
optional=[]
Expand Down Expand Up @@ -852,6 +860,49 @@ def get_deploy_key_command(self, data, user_data, org, repo, id):
send_info(data["channel"],
"@{}: Deploy Key ID `{}`: ```{}```".format(user_data["name"], id, deploy_key['key']), markdown=True)

@hubcommander_command(
name="!SetTopics",
usage="!SetTopics <OrgThatContainsRepo> <RepoToSetTopicsOn> <CommaSeparatedListOfTopics>",
description="This sets (or clears) the topics for a repository on GitHub.",
required=[
dict(name="org", properties=dict(type=str, help="The organization that contains the repo."),
validation_func=lookup_real_org, validation_func_kwargs={}),
dict(name="repo", properties=dict(type=str, help="The name of the repo to set the topics on."),
lowercase=False, validation_func=extract_repo_name, validation_func_kwargs={}),
],
optional=[
dict(name="topics", properties=dict(nargs="?", default="", type=str,
help="A comma separated list of topics to set on a repo. If"
" omitted, this will clear out the topics."
"Note: This will replace all existing topics."))
]
)
@auth()
def set_repo_topics_command(self, data, user_data, org, repo, topics):
# Make the topics a list:
if topics == "":
topic_list = []
else:
topic_list = topics.split(",")

# Output that we are doing work:
send_info(data["channel"], "@{}: Working, Please wait...".format(user_data["name"]))

# Set the topics:
if self.set_repo_topics(data, user_data, org, repo, topic_list):
# Done:
if len(topic_list) == 0:
send_info(data["channel"],
"@{}: The repo: {repo}'s topics were cleared.".format(user_data["name"], repo=repo),
markdown=True)

else:
send_info(data["channel"],
"@{}: The topics: `{topics}` were applied "
"to the repo: {repo}".format(user_data["name"], topics=",".join(topic_list), repo=repo),
markdown=True)

def check_if_repo_exists(self, data, user_data, reponame, real_org):
try:
result = self.check_gh_for_existing_repo(reponame, real_org)
Expand Down Expand Up @@ -921,6 +972,22 @@ def get_repo_prs(self, data, user_data, reponame, real_org, state, **kwargs):
"Here are the details: {}".format(user_data["name"], str(e)))
return False

def set_repo_topics(self, data, user_data, reponame, real_org, topics, **kwargs):
try:
return self.set_repo_topics_http(reponame, real_org, topics, **kwargs)

except requests.exceptions.RequestException as re:
send_error(data["channel"],
"@{}: Problem encountered while setting topics to the repository.\n"
"The response code from GitHub was: {}".format(user_data["name"], str(re)))
return False

except Exception as e:
send_error(data["channel"],
"@{}: Problem encountered while parsing the response.\n"
"Here are the details: {}".format(user_data["name"], str(e)))
return False

def get_repo_deploy_keys(self, data, user_data, reponame, real_org, **kwargs):
try:
return self.get_repo_deploy_keys_http(reponame, real_org, **kwargs)
Expand Down Expand Up @@ -1057,6 +1124,35 @@ def get_repo_pull_requests_http(self, repo, org, state, **kwargs):
.format(response.status_code)
raise requests.exceptions.RequestException(message)

def set_repo_topics_http(self, org, repo, topics, **kwargs):
"""
Set topics on a repo.
See: https://developer.github.com/v3/repos/#replace-all-topics-for-a-repository
:param org:
:param repo:
:param topics:
:param kwargs:
:return:
"""
headers = {
'Authorization': 'token {}'.format(self.token),
'Accept': "application/vnd.github.mercy-preview+json"
}

data = {"names": topics}

api_part = 'repos/{}/{}/topics'.format(org, repo)

response = requests.put('{}{}'.format(GITHUB_URL, api_part), data=json.dumps(data),
headers=headers, timeout=10)

if response.status_code != 200:
message = 'An error was encountered communicating with GitHub: Status Code: {}' \
.format(response.status_code)
raise requests.exceptions.RequestException(message)

return True

def add_outside_collab_to_repo(self, outside_collab_id, repo_name, real_org, permission):
headers = {
'Authorization': 'token {}'.format(self.token),
Expand Down
5 changes: 4 additions & 1 deletion docs/decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,17 @@ Instead, `@hubcommander_command` has an abstraction layer that can take care of

Here is an example of a multiple choice argument, as seen in the `!ListPRs` command:
```
dict(name="state", properties=dict(type=str, help="The state of the PR. Must be one of: `{values}`"),
dict(name="state", properties=dict(type=str.lower, help="The state of the PR. Must be one of: `{values}`"),
choices="permitted_states")
```

In here, `choices` refers to the name of the `plugin.commands[THECOMMANDHERE][AListOfAvailableChoices]`. It is the name
of the `list` within the plugin's command configuration that contains the available choices for the argument. This is done
because it allows the user of HubCommander to override this list in the plugin's configuration.

Also keep note of the `type`. The `type` in the example above is set to `str.lower`. This is to ensure that you
have case insensitivity in the parsed command. This is documented on StackOverflow [here](https://stackoverflow.com/questions/27616778/case-insensitive-argparse-choices/27616814).

Additionally, the `help` text is altered to include `{values}`. The `@hubcommander_command` decorator will properly format
the help text for that argument and fill in `{values}` with a comma separated list of the values in the specified list.

Expand Down
9 changes: 8 additions & 1 deletion tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def test_help_command_with_list(user_data, slack_client):
usage="!TestCommand <testThing>",
description="This is a test command to test help text for things in lists",
required=[
dict(name="test_thing", properties=dict(type=str, help="Must be one of: `{values}`"),
dict(name="test_thing", properties=dict(type=str.lower, help="Must be one of: `{values}`"),
choices="valid_values")
]
)
Expand All @@ -292,9 +292,15 @@ def the_command(self, data, user_data, test_thing):

tc = TestCommands()

# Will assert True
data = dict(text="!TestCommand one")
tc.the_command(data, user_data)

# Will ALSO assert True... we are making sure to lowercase the choices with str.lower as the type:
data = dict(text="!TestCommand ThReE")
tc.the_command(data, user_data)

# Will NOT assert true -- this will output help text:
data = dict(text="!TestCommand", channel="12345")
tc.the_command(data, user_data)

Expand All @@ -307,6 +313,7 @@ def the_command(self, data, user_data, test_thing):
slack_client.api_call.assert_called_with("chat.postMessage", channel="12345", as_user=True,
attachments=json.dumps([attachment]), text=" ")

# Will NOT assert true
data = dict(text="!TestCommand alskjfasdlkf", channel="12345")
tc.the_command(data, user_data)
attachment = {
Expand Down

0 comments on commit 213a03a

Please sign in to comment.