@@ -16,13 +16,14 @@ package cli
1616
1717import (
1818 "context"
19+ "errors"
1920 "fmt"
2021 "io"
21- "os"
2222
2323 "github.com/AlecAivazis/survey/v2"
2424 "github.com/mongodb/mongocli/internal/config"
2525 "github.com/mongodb/mongocli/internal/mongosh"
26+ "github.com/mongodb/mongocli/internal/prompt"
2627 "github.com/mongodb/mongocli/internal/store"
2728 "github.com/mongodb/mongocli/internal/validate"
2829 atlas "go.mongodb.org/atlas/mongodbatlas"
@@ -34,6 +35,7 @@ import (
3435type ProjectOrgsLister interface {
3536 Projects (* atlas.ListOptions ) (interface {}, error )
3637 Organizations (* atlas.OrganizationsListOptions ) (* atlas.Organizations , error )
38+ GetOrgProjects (string , * atlas.ListOptions ) (interface {}, error )
3739}
3840
3941type DefaultSetterOpts struct {
@@ -61,38 +63,67 @@ func (opts *DefaultSetterOpts) IsOpsManager() bool {
6163 return opts .Service == config .OpsManagerService
6264}
6365
66+ const resultsLimit = 500
67+
68+ var (
69+ errTooManyResults = errors .New ("too many results" )
70+ errNoResults = errors .New ("no results" )
71+ )
72+
6473// Projects fetches projects and returns then as a slice of the format `nameIDFormat`,
6574// and a map such as `map[nameIDFormat]=ID`.
6675// This is necessary as we can only prompt using `nameIDFormat`
67- // and we want them to get the ID mapping to store on the config.
68- func (opts * DefaultSetterOpts ) Projects () (pMap map [string ]string , pSlice []string , err error ) {
69- projects , err := opts .Store .Projects (nil )
76+ // and we want them to get the ID mapping to store in the config.
77+ func (opts * DefaultSetterOpts ) projects () (pMap map [string ]string , pSlice []string , err error ) {
78+ var projects interface {}
79+ if opts .OrgID == "" {
80+ projects , err = opts .Store .Projects (nil )
81+ } else {
82+ projects , err = opts .Store .GetOrgProjects (opts .OrgID , & atlas.ListOptions {ItemsPerPage : resultsLimit })
83+ }
7084 if err != nil {
71- _ , _ = fmt .Fprintf (os .Stderr , "there was a problem fetching projects: %s\n " , err )
7285 return nil , nil , err
7386 }
74- if opts .IsCloud () {
75- pMap , pSlice = atlasProjects (projects .(* atlas.Projects ).Results )
76- } else {
77- pMap , pSlice = omProjects (projects .(* opsmngr.Projects ).Results )
87+ switch r := projects .(type ) {
88+ case * atlas.Projects :
89+ if r .TotalCount == 0 {
90+ return nil , nil , errNoResults
91+ }
92+ if r .TotalCount > resultsLimit {
93+ return nil , nil , errTooManyResults
94+ }
95+ pMap , pSlice = atlasProjects (r .Results )
96+ case * opsmngr.Projects :
97+ if r .TotalCount == 0 {
98+ return nil , nil , errNoResults
99+ }
100+ if r .TotalCount > resultsLimit {
101+ return nil , nil , errTooManyResults
102+ }
103+ pMap , pSlice = omProjects (r .Results )
78104 }
105+
79106 return pMap , pSlice , nil
80107}
81108
82109// Orgs fetches organizations and returns then as a slice of the format `nameIDFormat`,
83110// and a map such as `map[nameIDFormat]=ID`.
84111// This is necessary as we can only prompt using `nameIDFormat`
85112// and we want them to get the ID mapping to store on the config.
86- func (opts * DefaultSetterOpts ) Orgs () (oMap map [string ]string , oSlice []string , err error ) {
113+ func (opts * DefaultSetterOpts ) orgs () (oMap map [string ]string , oSlice []string , err error ) {
87114 includeDeleted := false
88- orgs , err := opts .Store .Organizations (& atlas.OrganizationsListOptions {IncludeDeletedOrgs : & includeDeleted })
89- if orgs != nil && orgs .TotalCount > len (orgs .Results ) {
90- orgs , err = opts .Store .Organizations (& atlas.OrganizationsListOptions {IncludeDeletedOrgs : & includeDeleted , ListOptions : atlas.ListOptions {ItemsPerPage : orgs .TotalCount }})
91- }
115+ pagination := & atlas.OrganizationsListOptions {IncludeDeletedOrgs : & includeDeleted }
116+ pagination .ItemsPerPage = resultsLimit
117+ orgs , err := opts .Store .Organizations (pagination )
92118 if err != nil {
93- _ , _ = fmt .Fprintf (os .Stderr , "there was a problem fetching orgs: %s\n " , err )
94119 return nil , nil , err
95120 }
121+ if orgs .TotalCount == 0 {
122+ return nil , nil , errNoResults
123+ }
124+ if orgs .TotalCount > resultsLimit {
125+ return nil , nil , errTooManyResults
126+ }
96127 oMap = make (map [string ]string , len (orgs .Results ))
97128 oSlice = make ([]string , len (orgs .Results ))
98129 for i , o := range orgs .Results {
@@ -103,6 +134,85 @@ func (opts *DefaultSetterOpts) Orgs() (oMap map[string]string, oSlice []string,
103134 return oMap , oSlice , nil
104135}
105136
137+ // AskProject will try to construct a select based on fetched projects.
138+ // If it fails or there are no projects to show we fallback to ask for project by ID.
139+ func (opts * DefaultSetterOpts ) AskProject () error {
140+ pMap , pSlice , err := opts .projects ()
141+ if err != nil {
142+ var target * atlas.ErrorResponse
143+ switch {
144+ case errors .Is (err , errNoResults ):
145+ _ , _ = fmt .Fprintln (opts .OutWriter , "You don't seem to have access to any project" )
146+ case errors .Is (err , errTooManyResults ):
147+ _ , _ = fmt .Fprintf (opts .OutWriter , "You have access to more than %d projects\n " , resultsLimit )
148+ case errors .As (err , & target ):
149+ _ , _ = fmt .Fprintf (opts .OutWriter , "There was an error fetching your projects: %s\n " , target .Detail )
150+ default :
151+ _ , _ = fmt .Fprintf (opts .OutWriter , "There was an error fetching your projects: %s\n " , err )
152+ }
153+ p := & survey.Confirm {
154+ Message : "Do you want to enter the Project ID manually?" ,
155+ }
156+ manually := true
157+ if err2 := survey .AskOne (p , & manually ); err2 != nil {
158+ return err2
159+ }
160+ if manually {
161+ p := prompt .NewProjectIDInput ()
162+ return survey .AskOne (p , & opts .ProjectID , survey .WithValidator (validate .OptionalObjectID ))
163+ }
164+ _ , _ = fmt .Fprint (opts .OutWriter , "Skipping default project setting\n " )
165+ return nil
166+ }
167+
168+ p := prompt .NewProjectSelect (pSlice )
169+ var projectID string
170+ if err := survey .AskOne (p , & projectID ); err != nil {
171+ return err
172+ }
173+ opts .ProjectID = pMap [projectID ]
174+ return nil
175+ }
176+
177+ // AskOrg will try to construct a select based on fetched organizations.
178+ // If it fails or there are no organizations to show we fallback to ask for org by ID.
179+ func (opts * DefaultSetterOpts ) AskOrg () error {
180+ oMap , oSlice , err := opts .orgs ()
181+ if err != nil {
182+ var target * atlas.ErrorResponse
183+ switch {
184+ case errors .Is (err , errNoResults ):
185+ _ , _ = fmt .Fprintln (opts .OutWriter , "You don't seem to have access to any organization" )
186+ case errors .Is (err , errTooManyResults ):
187+ _ , _ = fmt .Fprintf (opts .OutWriter , "You have access to more than %d organizations\n " , resultsLimit )
188+ case errors .As (err , & target ):
189+ _ , _ = fmt .Fprintf (opts .OutWriter , "There was an error fetching your organizations: %s\n " , target .Detail )
190+ default :
191+ _ , _ = fmt .Fprintf (opts .OutWriter , "There was an error fetching your organizations: %s\n " , err )
192+ }
193+ p := & survey.Confirm {
194+ Message : "Do you want to enter the Org ID manually?" ,
195+ }
196+ manually := true
197+ if err2 := survey .AskOne (p , & manually ); err2 != nil {
198+ return err2
199+ }
200+ if manually {
201+ p := prompt .NewOrgIDInput ()
202+ return survey .AskOne (p , & opts .OrgID , survey .WithValidator (validate .OptionalObjectID ))
203+ }
204+ _ , _ = fmt .Fprint (opts .OutWriter , "Skipping default organization setting\n " )
205+ return nil
206+ }
207+ p := prompt .NewOrgSelect (oSlice )
208+ var orgID string
209+ if err := survey .AskOne (p , & orgID ); err != nil {
210+ return err
211+ }
212+ opts .OrgID = oMap [orgID ]
213+ return nil
214+ }
215+
106216func (opts * DefaultSetterOpts ) SetUpProject () {
107217 if opts .ProjectID != "" {
108218 config .SetProjectID (opts .ProjectID )
0 commit comments