From b218ae19fc7995393fef99d12b504ed86140813b Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Mon, 31 Jul 2023 15:08:09 +0200 Subject: [PATCH 1/5] feat(gov,group): improve `draft-proposal` prompt --- x/gov/client/cli/prompt.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index a75d7c9eda87..3c98580a1f45 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -66,8 +66,14 @@ func Prompt[T any](data T, namePrefix string) (T, error) { // if the field is a struct skip or not slice of string or int then skip switch v.Field(i).Kind() { case reflect.Struct: - // TODO(@julienrbrt) in the future we can add a recursive call to Prompt - continue + result, err := Prompt(v.Field(i).Interface(), namePrefix) + if err != nil { + return data, err + } + + if v.Field(i).CanSet() { + v.Field(i).Set(reflect.ValueOf(result)) + } case reflect.Slice: if v.Field(i).Type().Elem().Kind() != reflect.String && v.Field(i).Type().Elem().Kind() != reflect.Int { continue @@ -76,7 +82,7 @@ func Prompt[T any](data T, namePrefix string) (T, error) { // create prompts prompt := promptui.Prompt{ - Label: fmt.Sprintf("Enter %s's %s", namePrefix, strings.ToLower(client.CamelCaseToString(v.Type().Field(i).Name))), + Label: fmt.Sprintf("Enter %s %s", namePrefix, strings.ToLower(client.CamelCaseToString(v.Type().Field(i).Name))), Validate: client.ValidatePromptNotEmpty, } From d60fd38d980516457c3a28b20cd24a147849783e Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Mon, 31 Jul 2023 16:30:31 +0200 Subject: [PATCH 2/5] updates --- x/gov/client/cli/prompt.go | 59 +++++++++++++++++++++++++++++++----- x/group/client/cli/prompt.go | 17 ++++++++--- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index 3c98580a1f45..8192719475bc 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -98,6 +98,7 @@ func Prompt[T any](data T, namePrefix string) (T, error) { prompt.Validate = client.ValidatePromptAddress } + // TODO(@julienrbrt) use scalar annotation instead of dumb string name matching if strings.Contains(fieldName, "addr") || strings.Contains(fieldName, "sender") || strings.Contains(fieldName, "voter") || @@ -156,9 +157,8 @@ type proposalType struct { } // Prompt the proposal type values and return the proposal and its metadata -func (p *proposalType) Prompt(cdc codec.Codec) (*proposal, types.ProposalMetadata, error) { - // set metadata - metadata, err := Prompt(types.ProposalMetadata{}, "proposal") +func (p *proposalType) Prompt(cdc codec.Codec, skipMetadata bool) (*proposal, types.ProposalMetadata, error) { + metadata, err := PromptMetadata(skipMetadata) if err != nil { return nil, metadata, fmt.Errorf("failed to set proposal metadata: %w", err) } @@ -207,8 +207,48 @@ func getProposalSuggestions() []string { return types } +// PromptMetadata prompts for proposal metadata or only title and summary if skip is true +func PromptMetadata(skip bool) (types.ProposalMetadata, error) { + var ( + metadata types.ProposalMetadata + err error + ) + + if !skip { + metadata, err = Prompt(types.ProposalMetadata{}, "proposal") + if err != nil { + return metadata, fmt.Errorf("failed to set proposal metadata: %w", err) + } + } else { + // prompt for title and summary + titlePrompt := promptui.Prompt{ + Label: "Enter proposal title", + Validate: client.ValidatePromptNotEmpty, + } + + metadata.Title, err = titlePrompt.Run() + if err != nil { + return metadata, fmt.Errorf("failed to set proposal title: %w", err) + } + + summaryPrompt := promptui.Prompt{ + Label: "Enter proposal summary", + Validate: client.ValidatePromptNotEmpty, + } + + metadata.Summary, err = summaryPrompt.Run() + if err != nil { + return metadata, fmt.Errorf("failed to set proposal summary: %w", err) + } + } + + return metadata, nil +} + // NewCmdDraftProposal let a user generate a draft proposal. func NewCmdDraftProposal() *cobra.Command { + flagSkipMetadata := "skip-metadata" + cmd := &cobra.Command{ Use: "draft-proposal", Short: "Generate a draft proposal json file. The generated proposal json contains only one message (skeleton).", @@ -266,7 +306,9 @@ func NewCmdDraftProposal() *cobra.Command { } } - result, metadata, err := proposal.Prompt(clientCtx.Codec) + skipMetadataPrompt, _ := cmd.Flags().GetBool(flagSkipMetadata) + + result, metadata, err := proposal.Prompt(clientCtx.Codec, skipMetadataPrompt) if err != nil { return err } @@ -275,17 +317,20 @@ func NewCmdDraftProposal() *cobra.Command { return err } - if err := writeFile(draftMetadataFileName, metadata); err != nil { - return err + if !skipMetadataPrompt { + if err := writeFile(draftMetadataFileName, metadata); err != nil { + return err + } } - fmt.Printf("The draft proposal has successfully been generated.\nProposals should contain off-chain metadata, please upload the metadata JSON to IPFS.\nThen, replace the generated metadata field with the IPFS CID.\n") + cmd.Println("The draft proposal has successfully been generated.\nProposals should contain off-chain metadata, please upload the metadata JSON to IPFS.\nThen, replace the generated metadata field with the IPFS CID.") return nil }, } flags.AddTxFlagsToCmd(cmd) + cmd.Flags().Bool(flagSkipMetadata, false, "skip metadata prompt") return cmd } diff --git a/x/group/client/cli/prompt.go b/x/group/client/cli/prompt.go index 7b4a2fc4507c..69e027516a52 100644 --- a/x/group/client/cli/prompt.go +++ b/x/group/client/cli/prompt.go @@ -30,9 +30,9 @@ type proposalType struct { } // Prompt the proposal type values and return the proposal and its metadata. -func (p *proposalType) Prompt(cdc codec.Codec) (*Proposal, govtypes.ProposalMetadata, error) { +func (p *proposalType) Prompt(cdc codec.Codec, skipMetadata bool) (*Proposal, govtypes.ProposalMetadata, error) { // set metadata - metadata, err := govcli.Prompt(govtypes.ProposalMetadata{}, "proposal") + metadata, err := govcli.PromptMetadata(skipMetadata) if err != nil { return nil, metadata, fmt.Errorf("failed to set proposal metadata: %w", err) } @@ -86,6 +86,8 @@ func (p *proposalType) Prompt(cdc codec.Codec) (*Proposal, govtypes.ProposalMeta // NewCmdDraftProposal let a user generate a draft proposal. func NewCmdDraftProposal() *cobra.Command { + flagSkipMetadata := "skip-metadata" + cmd := &cobra.Command{ Use: "draft-proposal", Short: "Generate a draft proposal json file. The generated proposal json contains only one message (skeleton).", @@ -137,7 +139,9 @@ func NewCmdDraftProposal() *cobra.Command { panic("unexpected proposal type") } - result, metadata, err := proposal.Prompt(clientCtx.Codec) + skipMetadataPrompt, _ := cmd.Flags().GetBool(flagSkipMetadata) + + result, metadata, err := proposal.Prompt(clientCtx.Codec, skipMetadataPrompt) if err != nil { return err } @@ -146,8 +150,10 @@ func NewCmdDraftProposal() *cobra.Command { return err } - if err := writeFile(draftMetadataFileName, metadata); err != nil { - return err + if !skipMetadataPrompt { + if err := writeFile(draftMetadataFileName, metadata); err != nil { + return err + } } fmt.Printf("The draft proposal has successfully been generated.\nProposals should contain off-chain metadata, please upload the metadata JSON to IPFS.\nThen, replace the generated metadata field with the IPFS CID.\n") @@ -157,6 +163,7 @@ func NewCmdDraftProposal() *cobra.Command { } flags.AddTxFlagsToCmd(cmd) + cmd.Flags().Bool(flagSkipMetadata, false, "skip metadata prompt") return cmd } From 89f06677f75b5cd1f7dd2b72405b5564605cc6a1 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Mon, 31 Jul 2023 16:47:48 +0200 Subject: [PATCH 3/5] updates --- x/gov/client/cli/prompt.go | 10 ++-------- x/group/client/cli/prompt.go | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index 8192719475bc..c4ebc40bbd67 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -66,14 +66,8 @@ func Prompt[T any](data T, namePrefix string) (T, error) { // if the field is a struct skip or not slice of string or int then skip switch v.Field(i).Kind() { case reflect.Struct: - result, err := Prompt(v.Field(i).Interface(), namePrefix) - if err != nil { - return data, err - } - - if v.Field(i).CanSet() { - v.Field(i).Set(reflect.ValueOf(result)) - } + // TODO(@julienrbrt) in the future we can add a recursive call to Prompt + continue case reflect.Slice: if v.Field(i).Type().Elem().Kind() != reflect.String && v.Field(i).Type().Elem().Kind() != reflect.Int { continue diff --git a/x/group/client/cli/prompt.go b/x/group/client/cli/prompt.go index 69e027516a52..79b347f525a3 100644 --- a/x/group/client/cli/prompt.go +++ b/x/group/client/cli/prompt.go @@ -156,7 +156,7 @@ func NewCmdDraftProposal() *cobra.Command { } } - fmt.Printf("The draft proposal has successfully been generated.\nProposals should contain off-chain metadata, please upload the metadata JSON to IPFS.\nThen, replace the generated metadata field with the IPFS CID.\n") + cmd.Println("The draft proposal has successfully been generated.\nProposals should contain off-chain metadata, please upload the metadata JSON to IPFS.\nThen, replace the generated metadata field with the IPFS CID.") return nil }, From 75e8c89bbb2c5b531cf2f9a2d32f92ff8e6afd3c Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Mon, 31 Jul 2023 16:54:20 +0200 Subject: [PATCH 4/5] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e871464d68..e66fadf6b17a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* (x/group, x/gov) [#17220](https://github.com/cosmos/cosmos-sdk/pull/17220) Add `--skip-metadata` flag in `draft-proposal` to skip metadata prompt. * (x/group, x/gov) [#17109](https://github.com/cosmos/cosmos-sdk/pull/17109) Let proposal summary be 40x longer than metadata limit. * (all) [#16537](https://github.com/cosmos/cosmos-sdk/pull/16537) Properly propagated `fmt.Errorf` errors and using `errors.New` where appropriate. * (version) [#17096](https://github.com/cosmos/cosmos-sdk/pull/17096) Improve `getSDKVersion()` to handle module replacements From 3768c4ab6c73462a2fcb680a3e3921afeade1b64 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Tue, 1 Aug 2023 12:47:23 +0200 Subject: [PATCH 5/5] fix: remove url validation --- x/gov/client/cli/prompt.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index c4ebc40bbd67..2267ddc5052f 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -81,10 +81,6 @@ func Prompt[T any](data T, namePrefix string) (T, error) { } fieldName := strings.ToLower(v.Type().Field(i).Name) - // validation per field name - if strings.Contains(fieldName, "url") { - prompt.Validate = client.ValidatePromptURL - } if strings.EqualFold(fieldName, "authority") { // pre-fill with gov address