Skip to content
Closed
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
38 changes: 34 additions & 4 deletions shortcuts/base/base_form_questions_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,27 @@ var BaseFormQuestionsUpdate = common.Shortcut{
{Name: "base-token", Desc: "Base token (base_token)", Required: true},
{Name: "table-id", Desc: "table ID", Required: true},
{Name: "form-id", Desc: "form ID", Required: true},
{Name: "questions", Desc: `questions JSON array, max 10 items, each item must include "id". Supported fields: "id"(required),"title","description"(plain text or markdown link like [text](https://example.com)),"required","option_display_mode"(0=dropdown,1=vertical,2=horizontal,select only). E.g. '[{"id":"q_001","title":"Updated?","required":true}]'`, Required: true},
{Name: "questions", Desc: `questions JSON array, max 10 items, each item must include "id". Supported fields: "id"(required),"title","description"(plain text or markdown link like [text](https://example.com)),"required","option_display_mode"(0=dropdown,1=vertical,2=horizontal,select only),"attachment"({"file_types":["all"]},attachment only). E.g. '[{"id":"q_001","title":"Updated?","required":true}]'`, Required: true},
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
return common.NewDryRunAPI().
dr := common.NewDryRunAPI().
PATCH("/open-apis/base/v3/bases/:base_token/tables/:table_id/forms/:form_id/questions").
Set("base_token", runtime.Str("base-token")).
Set("table_id", runtime.Str("table-id")).
Set("form_id", runtime.Str("form-id"))
if questions, err := parseUpdateFormQuestions(runtime.Str("questions")); err == nil {
dr.Body(map[string]interface{}{"questions": questions})
Comment on lines +36 to +37
}
return dr
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
baseToken := runtime.Str("base-token")
tableId := runtime.Str("table-id")
formId := runtime.Str("form-id")
questionsJSON := runtime.Str("questions")

var questions []interface{}
if err := json.Unmarshal([]byte(questionsJSON), &questions); err != nil {
questions, err := parseUpdateFormQuestions(questionsJSON)
if err != nil {
return output.Errorf(output.ExitValidation, "invalid_json", "--questions must be a valid JSON array: %s", err)
}

Expand Down Expand Up @@ -74,3 +78,29 @@ var BaseFormQuestionsUpdate = common.Shortcut{
return nil
},
}

func parseUpdateFormQuestions(questionsJSON string) ([]interface{}, error) {
var questions []interface{}
if err := json.Unmarshal([]byte(questionsJSON), &questions); err != nil {
return nil, err
}
normalizeUpdateFormQuestionAttachments(questions)
return questions, nil
}

func normalizeUpdateFormQuestionAttachments(questions []interface{}) {
for _, question := range questions {
q, ok := question.(map[string]interface{})
if !ok || q["type"] != "attachment" {
continue
}
attachment, ok := q["attachment"].(map[string]interface{})
if !ok {
q["attachment"] = map[string]interface{}{"file_types": []interface{}{"all"}}
continue
}
if fileTypes, ok := attachment["file_types"]; !ok || fileTypes == nil {
attachment["file_types"] = []interface{}{"all"}
}
}
}
67 changes: 67 additions & 0 deletions shortcuts/base/base_form_questions_update_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT

package base

import (
"encoding/json"
"testing"
)

func TestNormalizeUpdateFormQuestionAttachments(t *testing.T) {
tests := []struct {
name string
input string
wantJSON string
}{
{
name: "attachment without attachment config defaults to all",
input: `[{"type":"attachment","title":"Upload"}]`,
wantJSON: `[{"attachment":{"file_types":["all"]},"title":"Upload","type":"attachment"}]`,
},
{
name: "attachment with explicit file_types preserved",
input: `[{"type":"attachment","title":"Upload","attachment":{"file_types":["image","pdf"]}}]`,
wantJSON: `[{"attachment":{"file_types":["image","pdf"]},"title":"Upload","type":"attachment"}]`,
},
{
name: "attachment with null file_types defaults to all",
input: `[{"type":"attachment","title":"Upload","attachment":{"file_types":null}}]`,
wantJSON: `[{"attachment":{"file_types":["all"]},"title":"Upload","type":"attachment"}]`,
},
{
name: "non-attachment type unchanged",
input: `[{"type":"text","title":"Name"}]`,
wantJSON: `[{"title":"Name","type":"text"}]`,
},
{
name: "mixed types only normalizes attachment",
input: `[{"type":"text","title":"Name"},{"type":"attachment","title":"Upload"}]`,
wantJSON: `[{"title":"Name","type":"text"},{"attachment":{"file_types":["all"]},"title":"Upload","type":"attachment"}]`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseUpdateFormQuestions(tt.input)
if err != nil {
t.Fatalf("parseUpdateFormQuestions error: %v", err)
}
gotBytes, _ := json.Marshal(got)
var gotMap, wantMap []map[string]interface{}
_ = json.Unmarshal(gotBytes, &gotMap)
_ = json.Unmarshal([]byte(tt.wantJSON), &wantMap)
gotBytes2, _ := json.Marshal(gotMap)
wantBytes2, _ := json.Marshal(wantMap)
if string(gotBytes2) != string(wantBytes2) {
t.Errorf("got %s, want %s", gotBytes2, wantBytes2)
}
})
}
}

func TestParseUpdateFormQuestionsInvalidJSON(t *testing.T) {
_, err := parseUpdateFormQuestions(`not-json`)
if err == nil {
t.Error("expected error for invalid JSON")
}
}
Loading