Skip to content

Commit

Permalink
Merge pull request #594 from wd60622/mentionable-users
Browse files Browse the repository at this point in the history
Use repo assignable / mentionable users in finder
  • Loading branch information
Alvaro Muñoz authored Sep 13, 2024
2 parents 50c6225 + 8c8fb7d commit ba5f510
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 60 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ require"octo".setup({
outdated_icon = "󰅒 ", -- outdated indicator
resolved_icon = "", -- resolved indicator
reaction_viewer_hint_icon = ""; -- marker for user reactions
users = "search", -- Users for assignees or reviewers. Values: "search" | "mentionable" | "assignable"
user_icon = ""; -- user icon
timeline_marker = ""; -- timeline marker
timeline_indent = "2"; -- timeline indentation
Expand Down Expand Up @@ -407,6 +408,16 @@ If no command is passed, the argument to `Octo` is treated as a URL from where a
- `<CR>`: Append Gist to buffer
[Available keys](https://cli.github.com/manual/gh_gist_list): `repo`\|`public`\|`secret`

5. Users in the assignee and reviewer commands:

- `search`: Dynamically search all GitHub users
- `mentionable`: List of *mentionable* users in current repo
- `assignable`: List of *assignable* users in current repo

Here, `search` is the default value and most broad. Both `assignable` and
`mentionable` are specific to the current repo. The `assignable` option is more
restrictive than `mentionable`.

## 🔥 Examples

```vim
Expand Down
22 changes: 22 additions & 0 deletions lua/octo/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ local M = {}
---@field default_merge_method string
---@field ssh_aliases {[string]:string}
---@field reaction_viewer_hint_icon string
---@field users string
---@field user_icon string
---@field comment_icon string
---@field outdated_icon string
Expand Down Expand Up @@ -96,6 +97,7 @@ function M.get_default_values()
default_merge_method = "commit",
ssh_aliases = {},
reaction_viewer_hint_icon = "",
users = "search",
user_icon = "",
comment_icon = "",
outdated_icon = "󰅒 ",
Expand Down Expand Up @@ -346,6 +348,25 @@ function M.validate_config()
validate_type(config.picker_config.mappings, "picker_config.mappings", "table")
end

local function validate_user_search()
if not validate_type(config.users, "users", "string") then
return
end

local valid_finders = { "search", "mentionable", "assignable" }

if not vim.tbl_contains(valid_finders, config.users) then
err(
"users." .. config.users,
string.format(
"Expected a valid user finder, received '%s', which is not a supported finder! Valid finders: %s",
config.users,
table.concat(valid_finders, ", ")
)
)
end
end

local function validate_aliases()
if not validate_type(config.ssh_aliases, "ssh_aliases", "table") then
return
Expand Down Expand Up @@ -395,6 +416,7 @@ function M.validate_config()
validate_type(config.gh_cmd, "gh_cmd", "string")
validate_type(config.gh_env, "gh_env", { "table", "function" })
validate_type(config.reaction_viewer_hint_icon, "reaction_viewer_hint_icon", "string")
validate_user_search()
validate_type(config.user_icon, "user_icon", "string")
validate_type(config.comment_icon, "comment_icon", "string")
validate_type(config.outdated_icon, "outdated_icon", "string")
Expand Down
36 changes: 36 additions & 0 deletions lua/octo/gh/graphql.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2679,6 +2679,42 @@ query {
}
]]

M.mentionable_users_query = [[
query($endCursor: String) {
repository(owner: "%s", name: "%s") {
mentionableUsers(first: 100, after: $endCursor) {
pageInfo {
hasNextPage
endCursor
startCursor
}
nodes {
id
login
}
}
}
}
]]

M.assignable_users_query = [[
query($endCursor: String) {
repository(owner: "%s", name: "%s") {
assignableUsers(first: 100, after: $endCursor) {
pageInfo {
hasNextPage
endCursor
startCursor
}
nodes {
id
login
}
}
}
}
]]

M.users_query = [[
query($endCursor: String) {
search(query: """%s""", type: USER, first: 100) {
Expand Down
182 changes: 122 additions & 60 deletions lua/octo/pickers/telescope/provider.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ local finders = require "telescope.finders"
local pickers = require "telescope.pickers"
local sorters = require "telescope.sorters"

local vim = vim

local M = {}

function M.not_implemented()
Expand Down Expand Up @@ -767,76 +769,136 @@ end
--
-- ASSIGNEES
--
local function get_user_requester()
return function(prompt)
-- skip empty queries
if not prompt or prompt == "" or utils.is_blank(prompt) then
return {}
end
local query = graphql("users_query", prompt)
local output = gh.run {
args = { "api", "graphql", "--paginate", "-f", string.format("query=%s", query) },
mode = "sync",
}
if output then
return {}
end

local users = {}
local orgs = {}
local responses = utils.get_pages(output)
for _, resp in ipairs(responses) do
for _, user in ipairs(resp.data.search.nodes) do
if not user.teams then
-- regular user
if not vim.tbl_contains(vim.tbl_keys(users), user.login) then
users[user.login] = {
id = user.id,
login = user.login,
}
end
elseif user.teams and user.teams.totalCount > 0 then
-- organization, collect all teams
if not vim.tbl_contains(vim.tbl_keys(orgs), user.login) then
orgs[user.login] = {
id = user.id,
login = user.login,
teams = user.teams.nodes,
}
else
vim.list_extend(orgs[user.login].teams, user.teams.nodes)
end
end
end
end

local results = {}
-- process orgs with teams
for _, user in pairs(users) do
table.insert(results, user)
end
for _, org in pairs(orgs) do
org.login = string.format("%s (%d)", org.login, #org.teams)
table.insert(results, org)
end
return results
end
end

local function get_users(query_name, node_name)
local repo = utils.get_remote_name()
local owner, name = utils.split_repo(repo)
local query = graphql(query_name, owner, name, { escape = true })
local output = gh.run {
args = { "api", "graphql", "--paginate", "-f", string.format("query=%s", query) },
mode = "sync",
}
if not output then
return {}
end

local responses = utils.get_pages(output)

local users = {}

for _, resp in ipairs(responses) do
local nodes = resp.data.repository[node_name].nodes
for _, user in ipairs(nodes) do
table.insert(users, {
id = user.id,
login = user.login,
})
end
end

return users
end

local function get_assignable_users()
return get_users("assignable_users_query", "assignableUsers")
end

local function get_mentionable_users()
return get_users("mentionable_users_query", "mentionableUsers")
end

local function create_user_finder()
local cfg = octo_config.values

local finder
local user_entry_maker = entry_maker.gen_from_user()
if cfg.users == "search" then
finder = finders.new_dynamic {
entry_maker = user_entry_maker,
fn = get_user_requester(),
}
elseif cfg.users == "assignable" then
finder = finders.new_table {
results = get_assignable_users(),
entry_maker = user_entry_maker,
}
else
finder = finders.new_table {
results = get_mentionable_users(),
entry_maker = user_entry_maker,
}
end

return finder
end

function M.select_user(cb)
local opts = vim.deepcopy(dropdown_opts)
opts.layout_config = {
width = 0.4,
height = 15,
}

--local queue = {}
local function get_user_requester()
return function(prompt)
-- skip empty queries
if not prompt or prompt == "" or utils.is_blank(prompt) then
return {}
end
local query = graphql("users_query", prompt)
local output = gh.run {
args = { "api", "graphql", "--paginate", "-f", string.format("query=%s", query) },
mode = "sync",
}
if output then
local users = {}
local orgs = {}
local responses = utils.get_pages(output)
for _, resp in ipairs(responses) do
for _, user in ipairs(resp.data.search.nodes) do
if not user.teams then
-- regular user
if not vim.tbl_contains(vim.tbl_keys(users), user.login) then
users[user.login] = {
id = user.id,
login = user.login,
}
end
elseif user.teams and user.teams.totalCount > 0 then
-- organization, collect all teams
if not vim.tbl_contains(vim.tbl_keys(orgs), user.login) then
orgs[user.login] = {
id = user.id,
login = user.login,
teams = user.teams.nodes,
}
else
vim.list_extend(orgs[user.login].teams, user.teams.nodes)
end
end
end
end

local results = {}
-- process orgs with teams
for _, user in pairs(users) do
table.insert(results, user)
end
for _, org in pairs(orgs) do
org.login = string.format("%s (%d)", org.login, #org.teams)
table.insert(results, org)
end
return results
else
return {}
end
end
end
local finder = create_user_finder()

pickers
.new(opts, {
finder = finders.new_dynamic {
entry_maker = entry_maker.gen_from_user(),
fn = get_user_requester(),
},
finder = finder,
sorter = sorters.get_fuzzy_file(opts),
attach_mappings = function()
actions.select_default:replace(function(prompt_bufnr)
Expand Down

0 comments on commit ba5f510

Please sign in to comment.