Skip to content

Commit

Permalink
List team memberships for chats
Browse files Browse the repository at this point in the history
  • Loading branch information
joshblum committed Oct 7, 2019
1 parent 8baecce commit c12662c
Show file tree
Hide file tree
Showing 13 changed files with 363 additions and 173 deletions.
13 changes: 12 additions & 1 deletion go/chat/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2903,7 +2903,7 @@ func (h *Server) SetBotMemberSettings(ctx context.Context, arg chat1.SetBotMembe
func (h *Server) GetBotMemberSettings(ctx context.Context, arg chat1.GetBotMemberSettingsArg) (res keybase1.TeamBotSettings, err error) {
var identBreaks []keybase1.TLFIdentifyFailure
ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
defer h.Trace(ctx, func() error { return err }, "SetBotMemberSettings")()
defer h.Trace(ctx, func() error { return err }, "GetBotMemberSettings")()
defer func() { err = h.fixupTeamErrorWithTLFName(ctx, arg.Username, arg.TlfName, err) }()
if _, err = utils.AssertLoggedInUID(ctx, h.G()); err != nil {
return res, err
Expand All @@ -2916,3 +2916,14 @@ func (h *Server) GetBotMemberSettings(ctx context.Context, arg chat1.GetBotMembe

return teams.GetBotSettingsByID(ctx, h.G().ExternalG(), teamID, arg.Username)
}

func (h *Server) TeamIDFromTLFName(ctx context.Context, arg chat1.TeamIDFromTLFNameArg) (res keybase1.TeamID, err error) {
var identBreaks []keybase1.TLFIdentifyFailure
ctx = globals.ChatCtx(ctx, h.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, h.identNotifier)
defer h.Trace(ctx, func() error { return err }, "TeamIDFromTLFName")()
if _, err = utils.AssertLoggedInUID(ctx, h.G()); err != nil {
return res, err
}

return h.teamIDFromTLFName(ctx, arg.MembersType, arg.TlfName, arg.TlfPublic)
}
64 changes: 56 additions & 8 deletions go/client/cmd_chat_listmembers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@ package client
import (
"context"
"fmt"
"os"

"github.com/keybase/cli"
"github.com/keybase/client/go/libcmdline"
"github.com/keybase/client/go/libkb"
"github.com/keybase/client/go/protocol/chat1"
"github.com/keybase/client/go/protocol/keybase1"
isatty "github.com/mattn/go-isatty"
)

type CmdChatListMembers struct {
libkb.Contextified

json, hasTTY bool
resolvingRequest chatConversationResolvingRequest
tlfName, topicName string
topicType chat1.TopicType
}
Expand All @@ -33,18 +37,29 @@ func newCmdChatListMembers(cl *libcmdline.CommandLine, g *libkb.GlobalContext) c
cl.ChooseCommand(NewCmdChatListMembersRunner(g), "list-members", c)
cl.SetLogForward(libcmdline.LogForwardNone)
},
Flags: mustGetChatFlags("topic-type"),
Flags: append(mustGetChatFlags("topic-type"), cli.BoolFlag{
Name: "j, json",
Usage: "Output memberships as JSON",
}),
}
}

func (c *CmdChatListMembers) Run() error {
ui := c.G().UI.GetTerminalUI()
ctx := context.Background()
if c.topicName != "" && c.topicName != "general" {
// conversation membership is based on server trust
return c.getUntrustedConvMemberList(ctx)
}

// determine membership via team load
return c.getTeamMemberList(ctx)
}

func (c *CmdChatListMembers) getUntrustedConvMemberList(ctx context.Context) error {
chatClient, err := GetChatLocalClient(c.G())
if err != nil {
return err
}

ctx := context.Background()
inboxRes, err := chatClient.FindConversationsLocal(ctx, chat1.FindConversationsLocalArg{
TlfName: c.tlfName,
MembersType: chat1.ConversationMembersType_TEAM,
Expand All @@ -63,24 +78,57 @@ func (c *CmdChatListMembers) Run() error {
return fmt.Errorf("ambiguous channel description, more than one conversation matches")
}

ui := c.G().UI.GetTerminalUI()
ui.Printf("Listing members in %s [#%s]:\n\n", c.tlfName, c.topicName)
for _, memb := range inboxRes.Conversations[0].Names() {
ui.Printf("%s\n", memb)
}

return nil
}

func (c *CmdChatListMembers) ParseArgv(ctx *cli.Context) (err error) {
if len(ctx.Args()) != 2 {
return fmt.Errorf("Incorrect usage.")
func (c *CmdChatListMembers) getTeamMemberList(ctx context.Context) error {
_, conversationInfo, err := resolveConversationForBotMember(c.G(), c.resolvingRequest, c.hasTTY)
if err != nil {
return err
}
chatClient, err := GetChatLocalClient(c.G())
if err != nil {
return err
}
teamID, err := chatClient.TeamIDFromTLFName(ctx, chat1.TeamIDFromTLFNameArg{
TlfName: conversationInfo.TlfName,
MembersType: conversationInfo.MembersType,
TlfPublic: conversationInfo.Visibility == keybase1.TLFVisibility_PUBLIC,
})
if err != nil {
return err
}

cli, err := GetTeamsClient(c.G())
if err != nil {
return err
}
details, err := cli.TeamGetByID(context.Background(), keybase1.TeamGetByIDArg{Id: teamID})
if err != nil {
return err
}

renderer := newTeamMembersRenderer(c.G(), c.json, false /*showInviteID*/)
return renderer.output(details, conversationInfo.TlfName, false /*verbose*/)
}

func (c *CmdChatListMembers) ParseArgv(ctx *cli.Context) (err error) {

c.json = ctx.Bool("json")
c.tlfName = ctx.Args().Get(0)
c.topicName = ctx.Args().Get(1)
c.hasTTY = isatty.IsTerminal(os.Stdin.Fd())
if c.topicType, err = parseConversationTopicType(ctx); err != nil {
return err
}
if c.resolvingRequest, err = parseConversationResolvingRequest(ctx, c.tlfName); err != nil {
return err
}
return nil
}

Expand Down
163 changes: 4 additions & 159 deletions go/client/cmd_team_list_memberships.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ package client

import (
"context"
"encoding/json"
"errors"
"fmt"
"sort"
"strings"
"text/tabwriter"

"github.com/keybase/cli"
Expand Down Expand Up @@ -135,7 +131,8 @@ func (c *CmdTeamListMemberships) runGet(cli keybase1.TeamsClient) error {
return err
}

return c.output(details)
renderer := newTeamMembersRenderer(c.G(), c.json, c.showInviteID)
return renderer.output(details, c.team, c.verbose)
}

func (c *CmdTeamListMemberships) runUser(cli keybase1.TeamsClient) error {
Expand Down Expand Up @@ -164,160 +161,8 @@ func (c *CmdTeamListMemberships) runUser(cli keybase1.TeamsClient) error {
return err
}

sort.Slice(list.Teams, func(i, j int) bool {
if list.Teams[i].FqName == list.Teams[j].FqName {
return list.Teams[i].Username < list.Teams[j].Username
}
return list.Teams[i].FqName < list.Teams[j].FqName
})

if c.json {
b, err := json.Marshal(list)
if err != nil {
return err
}
tui := c.G().UI.GetTerminalUI()
err = tui.OutputDesc(OutputDescriptorTeamList, string(b)+"\n")
return err
}

dui := c.G().UI.GetTerminalUI()
c.tabw = new(tabwriter.Writer)
c.tabw.Init(dui.OutputWriter(), 0, 8, 4, ' ', 0)

// Only print the username and full name columns when we're showing other users.
if c.showAll {
fmt.Fprintf(c.tabw, "Team\tRole\tUsername\tFull name\n")
} else {
fmt.Fprintf(c.tabw, "Team\tRole\tMembers\n")
}
for _, t := range list.Teams {
var role string
if t.Implicit != nil {
role += "implied admin"
}
if t.Role != keybase1.TeamRole_NONE {
if t.Implicit != nil {
role += ", "
}
role += strings.ToLower(t.Role.String())
}
if c.showAll {
var status string
switch t.Status {
case keybase1.TeamMemberStatus_RESET:
status = " (inactive due to account reset)"
case keybase1.TeamMemberStatus_DELETED:
status = " (inactive due to account delete)"
}
if len(t.FullName) > 0 && len(status) > 0 {
status = " " + status
}
fmt.Fprintf(c.tabw, "%s\t%s\t%s\t%s%s\n", t.FqName, role, t.Username, t.FullName, status)
} else {
fmt.Fprintf(c.tabw, "%s\t%s\t%d\n", t.FqName, role, t.MemberCount)
}
}
if c.showAll {
c.outputInvites(list.AnnotatedActiveInvites)
}

c.tabw.Flush()

return nil
}

func (c *CmdTeamListMemberships) output(details keybase1.TeamDetails) error {
if c.json {
return c.outputJSON(details)
}

return c.outputTerminal(details)
}

func (c *CmdTeamListMemberships) outputJSON(details keybase1.TeamDetails) error {
b, err := json.MarshalIndent(details, "", " ")
if err != nil {
return err
}
dui := c.G().UI.GetDumbOutputUI()
_, err = dui.Printf(string(b) + "\n")
return err
}

func (c *CmdTeamListMemberships) outputTerminal(details keybase1.TeamDetails) error {
dui := c.G().UI.GetTerminalUI()
c.tabw = new(tabwriter.Writer)
c.tabw.Init(dui.OutputWriter(), 0, 8, 2, ' ', 0)

c.outputRole("owner", details.Members.Owners)
c.outputRole("admin", details.Members.Admins)
c.outputRole("writer", details.Members.Writers)
c.outputRole("reader", details.Members.Readers)
c.outputRole("bot", details.Members.Bots)
c.outputRole("restricted_bot", details.Members.RestrictedBots)
c.outputInvites(details.AnnotatedActiveInvites)
c.tabw.Flush()

if c.verbose {
dui.Printf("At team key generation: %d\n", details.KeyGeneration)
}

return nil
}

func (c *CmdTeamListMemberships) outputRole(role string, members []keybase1.TeamMemberDetails) {
for _, member := range members {
var status string
switch member.Status {
case keybase1.TeamMemberStatus_RESET:
status = " (inactive due to account reset)"
case keybase1.TeamMemberStatus_DELETED:
status = " (inactive due to account delete)"
}
fmt.Fprintf(c.tabw, "%s\t%s\t%s\t%s%s\n", c.team, role, member.Username, member.FullName, status)
}
}

func (c *CmdTeamListMemberships) formatInviteName(invite keybase1.AnnotatedTeamInvite) (res string) {
res = string(invite.Name)
category, err := invite.Type.C()
if err == nil {
switch category {
case keybase1.TeamInviteCategory_SBS:
res = fmt.Sprintf("%s@%s", invite.Name, string(invite.Type.Sbs()))
case keybase1.TeamInviteCategory_SEITAN:
if res == "" {
res = "<token without label>"
}
}
}
return res
}

func (c *CmdTeamListMemberships) outputInvites(invites map[keybase1.TeamInviteID]keybase1.AnnotatedTeamInvite) {
for _, invite := range invites {
category, err := invite.Type.C()
if err != nil {
category = keybase1.TeamInviteCategory_UNKNOWN
}
trailer := fmt.Sprintf("(* invited by %s, awaiting acceptance)", invite.InviterUsername)
switch category {
case keybase1.TeamInviteCategory_EMAIL:
trailer = fmt.Sprintf("(* invited via email by %s, awaiting acceptance)", invite.InviterUsername)
case keybase1.TeamInviteCategory_SEITAN:
inviteIDTrailer := ""
if c.showInviteID {
// Show invite IDs for SEITAN tokens, which can be used to cancel the invite.
inviteIDTrailer = fmt.Sprintf(" (Invite ID: %s)", invite.Id)
}
trailer = fmt.Sprintf("(* invited via secret token by %s, awaiting acceptance)%s",
invite.InviterUsername, inviteIDTrailer)
}
fmtstring := "%s\t%s*\t%s\t%s\n"
fmt.Fprintf(c.tabw, fmtstring, invite.TeamName, strings.ToLower(invite.Role.String()),
c.formatInviteName(invite), trailer)
}
renderer := newTeamMembersRenderer(c.G(), c.json, c.showInviteID)
return renderer.outputTeams(list, c.showAll)
}

func (c *CmdTeamListMemberships) GetUsage() libkb.Usage {
Expand Down
Loading

0 comments on commit c12662c

Please sign in to comment.