From fd774dd7105cfe4ebe44dc49e40549e815b51897 Mon Sep 17 00:00:00 2001 From: debidong <1953531014@qq.com> Date: Tue, 12 May 2026 11:26:39 +0800 Subject: [PATCH] feat: add statuspage migration url name flag --- go.mod | 2 +- go.sum | 2 + internal/cli/status_page_migrate.go | 3 ++ internal/cli/status_page_migrate_test.go | 50 ++++++++++++++++++++++++ skills/flashduty-statuspage/SKILL.md | 3 ++ 5 files changed, 59 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3853d5f..caa89a5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/flashcatcloud/flashduty-cli go 1.25.1 require ( - github.com/flashcatcloud/flashduty-sdk v0.7.0 + github.com/flashcatcloud/flashduty-sdk v0.8.1-0.20260512032214-576d76bd987a github.com/spf13/cobra v1.10.2 golang.org/x/term v0.42.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 4138bd9..d5c6730 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/flashcatcloud/flashduty-sdk v0.7.0 h1:yPW8ghyHB60/34fz5sBITXhMWtbsm2mxYVFORgs+jpE= github.com/flashcatcloud/flashduty-sdk v0.7.0/go.mod h1:dG4eJfdZaj4jNBMwEexbfK/3PmcIMhNeJ88L/DcZzUY= +github.com/flashcatcloud/flashduty-sdk v0.8.1-0.20260512032214-576d76bd987a h1:nnMflbhcqVskLh22MaUpfXesqNegZ0uWUFd2sbenwT8= +github.com/flashcatcloud/flashduty-sdk v0.8.1-0.20260512032214-576d76bd987a/go.mod h1:dG4eJfdZaj4jNBMwEexbfK/3PmcIMhNeJ88L/DcZzUY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/internal/cli/status_page_migrate.go b/internal/cli/status_page_migrate.go index 4dec034..d921108 100644 --- a/internal/cli/status_page_migrate.go +++ b/internal/cli/status_page_migrate.go @@ -25,6 +25,7 @@ func newStatusPageMigrateStructureCmd() *cobra.Command { var source string var sourcePageID string var sourceAPIKey string + var urlName string cmd := &cobra.Command{ Use: "structure", @@ -37,6 +38,7 @@ func newStatusPageMigrateStructureCmd() *cobra.Command { result, err := ctx.Client.StartStatusPageMigration(cmdContext(ctx.Cmd), &flashduty.StartStatusPageMigrationInput{ SourceAPIKey: sourceAPIKey, SourcePageID: sourcePageID, + URLName: urlName, }) if err != nil { return err @@ -50,6 +52,7 @@ func newStatusPageMigrateStructureCmd() *cobra.Command { cmd.Flags().StringVar(&source, "from", "", "Migration source provider (required)") cmd.Flags().StringVar(&sourcePageID, "source-page-id", "", "Source page ID in the provider (required)") cmd.Flags().StringVar(&sourceAPIKey, "api-key", "", "Source provider API key (required)") + cmd.Flags().StringVar(&urlName, "url-name", "", "Optional URL name for a newly created Flashduty public status page; fails if the source page is already mapped to a different URL name") _ = cmd.MarkFlagRequired("from") _ = cmd.MarkFlagRequired("source-page-id") _ = cmd.MarkFlagRequired("api-key") diff --git a/internal/cli/status_page_migrate_test.go b/internal/cli/status_page_migrate_test.go index 661ef30..dc5430b 100644 --- a/internal/cli/status_page_migrate_test.go +++ b/internal/cli/status_page_migrate_test.go @@ -77,6 +77,9 @@ func TestCommandStatusPageMigrateStructureSendsSDKInput(t *testing.T) { if gotInput.SourcePageID != "src-1" { t.Errorf("SourcePageID = %q, want src-1", gotInput.SourcePageID) } + if gotInput.URLName != "" { + t.Errorf("URLName = %q, want empty", gotInput.URLName) + } if !strings.Contains(out, "Job ID: job-1") { t.Errorf("missing job id in output:\n%s", out) } @@ -85,6 +88,53 @@ func TestCommandStatusPageMigrateStructureSendsSDKInput(t *testing.T) { } } +func TestCommandStatusPageMigrateStructureSendsURLName(t *testing.T) { + saveAndResetGlobals(t) + + var gotInput *flashduty.StartStatusPageMigrationInput + mock := &mockStatusPageMigrate{ + startStructure: func(_ context.Context, input *flashduty.StartStatusPageMigrationInput) (*flashduty.StartStatusPageMigrationOutput, error) { + gotInput = input + return &flashduty.StartStatusPageMigrationOutput{JobID: "job-url"}, nil + }, + } + newClientFn = func() (flashdutyClient, error) { return mock, nil } + + _, err := execCommand("statuspage", "migrate", "structure", + "--from", "atlassian", + "--source-page-id", "src-1", + "--api-key", "atlassian-secret", + "--url-name", "customer-facing-status", + ) + if err != nil { + t.Fatalf("execCommand: %v", err) + } + + if gotInput == nil { + t.Fatal("expected input to be captured") + } + if gotInput.URLName != "customer-facing-status" { + t.Errorf("URLName = %q, want customer-facing-status", gotInput.URLName) + } +} + +func TestCommandStatusPageMigrateStructureHelpDescribesURLNameBehavior(t *testing.T) { + cmd := newStatusPageMigrateStructureCmd() + flag := cmd.Flags().Lookup("url-name") + if flag == nil { + t.Fatal("expected --url-name flag to be registered") + } + + for _, want := range []string{ + "newly created Flashduty public status page", + "already mapped to a different URL name", + } { + if !strings.Contains(flag.Usage, want) { + t.Errorf("--url-name usage missing %q: %s", want, flag.Usage) + } + } +} + func TestCommandStatusPageMigrateStructureRejectsUnsupportedSource(t *testing.T) { saveAndResetGlobals(t) diff --git a/skills/flashduty-statuspage/SKILL.md b/skills/flashduty-statuspage/SKILL.md index 2c16e8f..69c4782 100644 --- a/skills/flashduty-statuspage/SKILL.md +++ b/skills/flashduty-statuspage/SKILL.md @@ -117,6 +117,7 @@ flashduty statuspage migrate structure --from atlassian --source-page-id -- | `--from` | string | Source provider, currently only `atlassian` (**required**) | | `--source-page-id` | string | Page ID in the source provider (**required**) | | `--api-key` | string | Source provider API key (**required**) | +| `--url-name` | string | Optional URL name for the newly created Flashduty public page. It is normalized with the same slug rules as page creation and only applies when the source page is not already mapped. If the source page already maps to a different Flashduty URL name, the command fails instead of changing the existing page. | Returns a job ID plus the command to poll its status. Human output shows the new Flashduty `target_page_id` once the job reaches the `completed` phase — capture that for the subscriber migration. @@ -210,6 +211,7 @@ Import structure first, verify, then import subscribers. flashduty statuspage migrate structure \ --from atlassian \ --source-page-id page_atl_123 \ + --url-name customer-facing-status \ --api-key "$ATLASSIAN_STATUSPAGE_API_KEY" # → captures Job ID: str_abc @@ -250,6 +252,7 @@ flashduty statuspage migrate status --job-id str_abc - **Page ID** (int) is the Flashduty status page primary key. **Change ID** (int) is the ID of an incident/maintenance within a page. Don't confuse them. - **Migration is async.** `migrate structure` and `migrate email-subscribers` return immediately with a job ID; the actual work happens on the backend. - **Two migration jobs, not one.** Structure + history run separately from subscribers. This is deliberate — subscriber import triggers verification emails, so operators verify content first. +- **`--url-name` is create-only.** Use it to choose the public URL slug for a newly created Flashduty page. It does not rename an existing mapped target page; if the Atlassian page has already been migrated to another URL name, retry without `--url-name` or use the mapped page. - **Migration phases** for the structure job progress in order: `components` → `sections` → `history` (incidents + maintenances) → `templates`. The subscribers job has a single `subscribers` phase. - **Terminal statuses:** `completed`, `failed`, `cancelled`. Stop polling once any of these appears. - **`--notify` is subscriber-visible.** In `create-incident`, omit or set `--notify=false` for silent incidents; set `--notify` when you want an announcement.