Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 40 additions & 70 deletions cmd/app/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,58 +97,8 @@ func LinkCommandRunE(ctx context.Context, clients *shared.ClientFactory, app *ty
// Add empty line between executed command and first output
clients.IO.PrintInfo(ctx, false, "")

err = LinkExistingApp(ctx, clients, app, false)
if err != nil {
return err
}

return nil
}

// LinkAppHeaderSection displays a section explaining how to find existing apps.
// External callers can use extraSecondaryText to show additional information.
// When shouldConfirm is true, additional information is included in the header
// explaining how to link apps, in case the user declines.
func LinkAppHeaderSection(ctx context.Context, clients *shared.ClientFactory, shouldConfirm bool) {
var secondaryText = []string{
"Add an existing app from app settings",
"Find your existing apps at: " + style.Underline("https://api.slack.com/apps"),
}

if shouldConfirm {
secondaryText = append(secondaryText, "Manually add apps later with "+style.Commandf("app link", true))
}

clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{
Emoji: "house",
Text: "App Link",
Secondary: secondaryText,
}))
}

// LinkExistingApp prompts for an existing App ID and saves the details to the project.
// When shouldConfirm is true, a confirmation prompt will ask the user if they want to
// link an existing app and additional information is included in the header.
// The shouldConfirm option is encouraged for third-party callers.
func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *types.App, shouldConfirm bool) (err error) {
// Header section
LinkAppHeaderSection(ctx, clients, shouldConfirm)

// Confirm to add an existing app; useful for third-party callers
if shouldConfirm {
proceed, err := clients.IO.ConfirmPrompt(ctx, LinkAppConfirmPromptText, true)
if err != nil {
clients.IO.PrintDebug(ctx, "Error prompting to add an existing app: %s", err)
return err
}

// Add newline to match the trailing newline inserted from the footer section
clients.IO.PrintInfo(ctx, false, "")

if !proceed {
return nil
}
}
LinkAppHeaderSection(ctx, clients)

// App Manifest section
manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx)
Expand All @@ -166,6 +116,45 @@ func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *ty
},
}))

err = LinkExistingApp(ctx, clients, app)
if err != nil {
return err
}

// App summary section
clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{
Emoji: "house",
Text: "App",
Secondary: FormatListSuccess([]types.App{*app}),
}))
Comment on lines +124 to +129

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🏗️ question: I'm a fan of separating outputs and logic overall but am curious about including this within LinkExistingApp to keep the function signature minimal? I share another comment about ongoing refactor but am curious if this seems right or not:

func LinkCommandRunE(ctx context.Context, clients *shared.ClientFactory, app *types.App)

📣 ramble: So that we avoid conditional statements in these functions!


// Footer section
clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{
Emoji: "house_with_garden",
Text: "App Link",
Secondary: []string{
"Added existing app to project",
},
}))

return nil
}

// LinkAppHeaderSection displays a section explaining how to find existing apps.
func LinkAppHeaderSection(ctx context.Context, clients *shared.ClientFactory) {
clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{
Emoji: "house",
Text: "App Link",
Secondary: []string{
"Add an existing app from app settings",
"Find your existing apps at: " + style.Underline("https://api.slack.com/apps"),
},
}))
}

// LinkExistingApp resolves app details, validates the app, and saves it to the
// project. It produces no output — callers handle their own display.
func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *types.App) (err error) {
// Prompt to get app details
var auth *types.SlackAuth
*app, auth, err = promptExistingApp(ctx, clients)
Expand All @@ -186,28 +175,9 @@ func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *ty
return err
}

// Footer section
LinkAppFooterSection(ctx, clients, app)

return nil
}

// LinkAppFooterSection displays the details of app that was added to the project.
func LinkAppFooterSection(ctx context.Context, clients *shared.ClientFactory, app *types.App) {
clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{
Emoji: "house",
Text: "App",
Secondary: formatListSuccess([]types.App{*app}),
}))
clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{
Emoji: "house_with_garden",
Text: "App Link",
Secondary: []string{
"Added existing app to project",
},
}))
}

// promptExistingApp gathers details to represent app information
func promptExistingApp(ctx context.Context, clients *shared.ClientFactory) (types.App, *types.SlackAuth, error) {
slackAuth, err := prompts.PromptTeamSlackAuth(ctx, clients, "Select the existing app team", nil)
Expand Down
53 changes: 8 additions & 45 deletions cmd/app/link_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,53 +572,16 @@ func Test_Apps_Link(t *testing.T) {
}

func Test_Apps_LinkAppHeaderSection(t *testing.T) {
tests := map[string]struct {
shouldConfirm bool
expectedOutputs []string
unexpectedOutputs []string
}{
"When shouldConfirm is false": {
shouldConfirm: false,
expectedOutputs: []string{
"Add an existing app from app settings",
"Find your existing apps at: https://api.slack.com/apps",
},
unexpectedOutputs: []string{
"Manually add apps later with",
},
},
"When shouldConfirm is true": {
shouldConfirm: true,
expectedOutputs: []string{
"Add an existing app from app settings",
"Find your existing apps at: https://api.slack.com/apps",
"Manually add apps later with",
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// Create mocks
ctx := slackcontext.MockContext(t.Context())
clientsMock := shared.NewClientsMock()
clientsMock.AddDefaultMocks()
ctx := slackcontext.MockContext(t.Context())
clientsMock := shared.NewClientsMock()
clientsMock.AddDefaultMocks()
clients := shared.NewClientFactory(clientsMock.MockClientFactory())

// Create clients that is mocked for testing
clients := shared.NewClientFactory(clientsMock.MockClientFactory())
LinkAppHeaderSection(ctx, clients)

// Run the test
LinkAppHeaderSection(ctx, clients, tc.shouldConfirm)

// Assertions
output := clientsMock.GetCombinedOutput()
for _, expectedOutput := range tc.expectedOutputs {
require.Contains(t, output, expectedOutput)
}
for _, unexpectedOutput := range tc.unexpectedOutputs {
require.NotContains(t, output, unexpectedOutput)
}
})
}
output := clientsMock.GetCombinedOutput()
require.Contains(t, output, "Add an existing app from app settings")
require.Contains(t, output, "Find your existing apps at: https://api.slack.com/apps")
}

func setupAppLinkCommandMocks(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
Expand Down
6 changes: 3 additions & 3 deletions cmd/app/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ func runListCommand(cmd *cobra.Command, clients *shared.ClientFactory) error {
clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{
Emoji: "house_buildings",
Text: "Apps",
Secondary: formatListSuccess(envs),
Secondary: FormatListSuccess(envs),
}))
return nil
}

// formatListSuccess formats details about the list of project apps
func formatListSuccess(apps []types.App) (secondaryText []string) {
// FormatListSuccess formats details about the list of project apps
func FormatListSuccess(apps []types.App) (secondaryText []string) {
for _, app := range apps {
if app.AppID == "" {
continue
Expand Down
2 changes: 1 addition & 1 deletion cmd/app/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func TestAppsListFormat(t *testing.T) {
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
listFlags = tc.Flags
formattedList := formatListSuccess(tc.Apps)
formattedList := FormatListSuccess(tc.Apps)
for ii, value := range formattedList {
formattedList[ii] = strings.TrimRight(value, ":")
}
Expand Down
8 changes: 7 additions & 1 deletion cmd/project/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,15 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []
defer func() {
_ = os.Chdir(originalDir)
}()
if err := app.LinkExistingApp(ctx, clients, &types.App{}, false); err != nil {
linkedApp := &types.App{}
if err := app.LinkExistingApp(ctx, clients, linkedApp); err != nil {
return err
}
clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{
Emoji: "house",
Text: "App",
Secondary: app.FormatListSuccess([]types.App{*linkedApp}),
}))
}

printCreateSuccess(ctx, clients, appDirPath)
Expand Down
23 changes: 19 additions & 4 deletions cmd/project/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,26 @@ func projectInitCommandRunE(clients *shared.ClientFactory, cmd *cobra.Command, a
// Existing projects initialized always default to config.ManifestSourceLocal.
_ = create.InstallProjectDependencies(ctx, clients, projectDirPath)

// Add an existing app to the project
err = app.LinkExistingApp(ctx, clients, &types.App{}, true)
// Prompt to add an existing app to the project
app.LinkAppHeaderSection(ctx, clients)
clients.IO.PrintInfo(ctx, false, " %s\n", "Manually add apps later with "+style.Commandf("app link", true))
proceed, err := clients.IO.ConfirmPrompt(ctx, app.LinkAppConfirmPromptText, true)
if err != nil {
// Display the error but continue to init
clients.IO.PrintError(ctx, "%s", err.Error())
clients.IO.PrintDebug(ctx, "Error prompting to add an existing app: %s", err)
}
if proceed {
linkedApp := &types.App{}
err = app.LinkExistingApp(ctx, clients, linkedApp)
if err != nil {
// Display the error but continue to init
clients.IO.PrintError(ctx, "%s", err.Error())
} else {
clients.IO.PrintInfo(ctx, false, "%s", style.Sectionf(style.TextSection{
Emoji: "house",
Text: "App",
Secondary: app.FormatListSuccess([]types.App{*linkedApp}),
}))
}
}

printNextStepSection(ctx, clients, projectDirPath)
Expand Down
Loading