From e1e5321f2daecbdc329e855972afd49aa1235216 Mon Sep 17 00:00:00 2001 From: alick-liming Date: Fri, 9 Oct 2026 13:55:02 +0800 Subject: [PATCH 1/2] fix(integrations): align SDK with public integration contract --- integration_contract_test.go | 99 ++++++++ integrations.go | 6 +- models_gen.go | 110 +++++++-- openapi/openapi.en.json | 441 ++++++++++++++++++++++------------- openapi/openapi.zh.json | 441 ++++++++++++++++++++++------------- 5 files changed, 760 insertions(+), 337 deletions(-) create mode 100644 integration_contract_test.go diff --git a/integration_contract_test.go b/integration_contract_test.go new file mode 100644 index 0000000..10a4812 --- /dev/null +++ b/integration_contract_test.go @@ -0,0 +1,99 @@ +package flashduty + +import ( + "encoding/json" + "reflect" + "testing" +) + +func TestIntegrationResponseContract(t *testing.T) { + for _, tc := range []struct { + name string + target any + body string + }{ + {"list item", new(IntegrationItem), `{"integration_id":42,"plugin_id":9,"account_id":100,"data_source_id":42,"exclusive_data_source_id":0,"integration_key":"test-key","settings":{"password":"test-password"},"editable":true,"creator_id":11,"updated_by":12,"no_editable":false}`}, + {"detail", new(IntegrationDetail), `{"integration_id":42,"plugin_id":9,"integration_key":"test-key","settings":{"headers":{"Authorization":"test-token"}},"editable":true,"creator_id":11,"updated_by":12,"no_editable":false,"test":{"success":false,"message":"test connection failed"}}`}, + {"create", new(CreateIntegrationResponse), `{"integration_id":42,"integration_key":"test-key","test":{"success":false,"message":"test connection failed"}}`}, + } { + t.Run(tc.name, func(t *testing.T) { + if err := json.Unmarshal([]byte(tc.body), tc.target); err != nil { + t.Fatal(err) + } + encoded, err := json.Marshal(tc.target) + if err != nil { + t.Fatal(err) + } + var want, got map[string]any + if err := json.Unmarshal([]byte(tc.body), &want); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(encoded, &got); err != nil { + t.Fatal(err) + } + for field, value := range want { + if !reflect.DeepEqual(got[field], value) { + t.Errorf("%s lost or changed: got %#v, want %#v", field, got[field], value) + } + } + }) + } +} + +func TestIntegrationResponsePreservesAbsentTest(t *testing.T) { + for _, tc := range []struct { + name string + target any + }{ + {"detail", new(IntegrationDetail)}, + {"create", new(CreateIntegrationResponse)}, + } { + t.Run(tc.name, func(t *testing.T) { + if err := json.Unmarshal([]byte(`{"integration_id":42}`), tc.target); err != nil { + t.Fatal(err) + } + encoded, err := json.Marshal(tc.target) + if err != nil { + t.Fatal(err) + } + var got map[string]any + if err := json.Unmarshal(encoded, &got); err != nil { + t.Fatal(err) + } + if _, ok := got["test"]; ok { + t.Fatalf("absent test result became %s", encoded) + } + }) + } +} + +func TestIntegrationRequestContract(t *testing.T) { + for _, tc := range []struct { + name string + target any + body string + }{ + {"create", new(CreateIntegrationRequest), `{"plugin_type":"standard.alert","integration_key":"0123456789abcdef0123456789abcdef100","is_test":true,"settings":{"headers":{}}}`}, + {"update", new(UpdateIntegrationRequest), `{"integration_id":42,"team_id":0,"is_test":true,"settings":{"headers":{}}}`}, + } { + t.Run(tc.name, func(t *testing.T) { + if err := json.Unmarshal([]byte(tc.body), tc.target); err != nil { + t.Fatal(err) + } + encoded, err := json.Marshal(tc.target) + if err != nil { + t.Fatal(err) + } + var want, got map[string]any + if err := json.Unmarshal([]byte(tc.body), &want); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(encoded, &got); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("request fields lost: got %s, want %s", encoded, tc.body) + } + }) + } +} diff --git a/integrations.go b/integrations.go index 288b5fb..2631042 100644 --- a/integrations.go +++ b/integrations.go @@ -23,7 +23,7 @@ func (s *IntegrationsService) DatasourceImPersonTryLink(ctx context.Context, req // Get integration detail. // -// Return one integration, including its settings with sensitive values masked. +// Get a public integration with its stored settings and decrypted integration key. // // API: POST /integration/info (integration-api-read-info). func (s *IntegrationsService) IntegrationAPIReadInfo(ctx context.Context, req *GetIntegrationRequest) (*IntegrationDetail, *Response, error) { @@ -37,7 +37,7 @@ func (s *IntegrationsService) IntegrationAPIReadInfo(ctx context.Context, req *G // List integrations. // -// List the account's alert-source and change-source integrations. +// List public integrations with their stored settings, decrypted keys, and edit permissions. // // API: POST /integration/list (integration-api-read-list). func (s *IntegrationsService) IntegrationAPIReadList(ctx context.Context, req *ListIntegrationsRequest) (*ListIntegrationsResponse, *Response, error) { @@ -65,7 +65,7 @@ func (s *IntegrationsService) IntegrationAPIReadTypeList(ctx context.Context, re // Create integration. // -// Create an integration for an alert source or change source. +// Create a public integration for a supported alert, change, IM, or webhook type. // // API: POST /integration/create (integration-api-write-create). func (s *IntegrationsService) IntegrationAPIWriteCreate(ctx context.Context, req *CreateIntegrationRequest) (*CreateIntegrationResponse, *Response, error) { diff --git a/models_gen.go b/models_gen.go index 1d36a16..5df6b3f 100644 --- a/models_gen.go +++ b/models_gen.go @@ -2246,13 +2246,17 @@ type CreateInhibitRuleRequest struct { type CreateIntegrationRequest struct { // Free-form description, at most 499 characters. Description string `json:"description,omitempty" toon:"description,omitempty"` - // Integration name. 2–49 characters. + // Optional pre-generated key: 35 characters, ending with the account ID modulo 1000 padded to three digits; it must not already be in use. Omit to generate a key. + IntegrationKey string `json:"integration_key,omitempty" toon:"integration_key,omitempty"` + // Run the plugin test during creation; false when omitted. Inspect the returned `test.success` for the test outcome. + IsTest bool `json:"is_test,omitempty" toon:"is_test,omitempty"` + // Integration name, 2–49 characters when nonempty; defaults to the integration type name when omitted or empty. Name string `json:"name,omitempty" toon:"name,omitempty"` // Integration type. Must be one listed by `POST /integration/type/list` with `supports_api_create: true`. PluginType string `json:"plugin_type" toon:"plugin_type"` // Type-specific configuration; the accepted keys depend on `plugin_type`. Settings map[string]any `json:"settings,omitempty" toon:"settings,omitempty"` - // Owning team ID. + // Owning team ID; omit or use `0` for no team. A nonzero ID must belong to the current account. TeamID int64 `json:"team_id,omitempty" toon:"team_id,omitempty"` } @@ -2260,8 +2264,10 @@ type CreateIntegrationRequest struct { type CreateIntegrationResponse struct { // ID of the new integration. IntegrationID int64 `json:"integration_id" toon:"integration_id"` - // Key used to authenticate inbound pushes to this integration. Returned here only; fetch a new one with `POST /integration/key/rotate`. + // Integration key for inbound pushes; also readable from `/integration/list` and `/integration/info`. IntegrationKey string `json:"integration_key" toon:"integration_key"` + // Plugin test result, returned only when a test is performed. + Test *CreateIntegrationResponseTest `json:"test,omitempty" toon:"test,omitempty"` } // CreateSilenceRuleRequest is generated from the Flashduty OpenAPI schema. @@ -5680,48 +5686,94 @@ type InsightTopkAlertByLabelRequest struct { // IntegrationDetail is generated from the Flashduty OpenAPI schema. type IntegrationDetail struct { - Category any `json:"category" toon:"category"` - CreatedAt any `json:"created_at" toon:"created_at"` - Description any `json:"description" toon:"description"` - IntegrationID any `json:"integration_id" toon:"integration_id"` - LastTime any `json:"last_time" toon:"last_time"` - Name any `json:"name" toon:"name"` - PluginType any `json:"plugin_type" toon:"plugin_type"` - PluginTypeName any `json:"plugin_type_name" toon:"plugin_type_name"` - RefID any `json:"ref_id" toon:"ref_id"` - // Type-specific configuration. Sensitive values (endpoint, headers, secrets, passwords) are returned masked as `******`. - Settings map[string]any `json:"settings" toon:"settings"` - Status any `json:"status" toon:"status"` - TeamID any `json:"team_id" toon:"team_id"` - UpdatedAt any `json:"updated_at" toon:"updated_at"` + // Category the integration belongs to: `event.alert` alert events, `event.change` change events, `im` IM bots, `webhook` custom webhooks. + Category string `json:"category" toon:"category"` + // Unix timestamp in seconds when the integration was created. + CreatedAt Timestamp `json:"created_at" toon:"created_at"` + // ID of the member who created the integration. + CreatorID int64 `json:"creator_id" toon:"creator_id"` + // Free-form description. + Description string `json:"description" toon:"description"` + // Whether the current caller passes the integration object-permission check; write calls also require Integrations Manage. + Editable bool `json:"editable" toon:"editable"` + // Integration ID. + IntegrationID int64 `json:"integration_id" toon:"integration_id"` + // Decrypted integration key for inbound pushes. Empty when no key is stored; distinct from the account `app_key`. + IntegrationKey string `json:"integration_key" toon:"integration_key"` + // Unix timestamp in seconds of the most recent event received. `0` when no event has arrived yet. + LastTime Timestamp `json:"last_time" toon:"last_time"` + // Integration name. + Name string `json:"name" toon:"name"` + // Read-only flag. When true, the public APIs do not allow changes to this integration. + NoEditable bool `json:"no_editable" toon:"no_editable"` + // Internal integration type ID; use `plugin_type`, not this ID, when creating an integration. + PluginID int64 `json:"plugin_id" toon:"plugin_id"` + // Integration type, for example `standard.alert` or `zabbix.alert`. + PluginType string `json:"plugin_type" toon:"plugin_type"` + // Display name of the integration type, in the language of the request. + PluginTypeName string `json:"plugin_type_name" toon:"plugin_type_name"` + // Source reference ID, normally `a_` for an account-level integration; legacy `w_` references may also exist. Channel-owned `c_` integrations are excluded. + RefID string `json:"ref_id" toon:"ref_id"` + // Stored type-specific configuration, including credential values without masking; matches the legacy `/datasource/*` response behavior. + Settings map[string]any `json:"settings" toon:"settings"` + // Lifecycle status: `enabled` while the integration accepts events, `disabled` when it is paused. + Status string `json:"status" toon:"status"` + // ID of the team that owns the integration. `0` when it is not assigned to a team. + TeamID int64 `json:"team_id" toon:"team_id"` + // Plugin test result, returned only when a test is performed. + Test *IntegrationDetailTest `json:"test,omitempty" toon:"test,omitempty"` + // Unix timestamp in seconds when the integration was last updated. + UpdatedAt Timestamp `json:"updated_at" toon:"updated_at"` + // ID of the member who last updated the integration. + UpdatedBy int64 `json:"updated_by" toon:"updated_by"` } // IntegrationItem is generated from the Flashduty OpenAPI schema. type IntegrationItem struct { + // ID of the account that owns the integration. + AccountID int64 `json:"account_id" toon:"account_id"` // Category the integration belongs to: `event.alert` alert events, `event.change` change events, `im` IM bots, `webhook` custom webhooks. Category string `json:"category" toon:"category"` // Unix timestamp in seconds when the integration was created. CreatedAt Timestamp `json:"created_at" toon:"created_at"` + // ID of the member who created the integration. + CreatorID int64 `json:"creator_id" toon:"creator_id"` + // Legacy datasource ID, equal to `integration_id`. + DataSourceID int64 `json:"data_source_id" toon:"data_source_id"` // Free-form description. Description string `json:"description" toon:"description"` + // Whether the current caller passes the integration object-permission check; write calls also require Integrations Manage. + Editable bool `json:"editable" toon:"editable"` + // Legacy compatibility field; `0` for items returned by this public-integration list. + ExclusiveDataSourceID int64 `json:"exclusive_data_source_id" toon:"exclusive_data_source_id"` // Integration ID. IntegrationID int64 `json:"integration_id" toon:"integration_id"` + // Decrypted integration key for inbound pushes. Empty when no key is stored; distinct from the account `app_key`. + IntegrationKey string `json:"integration_key" toon:"integration_key"` // Unix timestamp in seconds of the most recent event received. `0` when no event has arrived yet. LastTime Timestamp `json:"last_time" toon:"last_time"` // Integration name. Name string `json:"name" toon:"name"` + // Read-only flag. When true, the public APIs do not allow changes to this integration. + NoEditable bool `json:"no_editable" toon:"no_editable"` + // Internal integration type ID; use `plugin_type`, not this ID, when creating an integration. + PluginID int64 `json:"plugin_id" toon:"plugin_id"` // Integration type, for example `standard.alert` or `zabbix.alert`. PluginType string `json:"plugin_type" toon:"plugin_type"` // Display name of the integration type, in the language of the request. PluginTypeName string `json:"plugin_type_name" toon:"plugin_type_name"` - // Source reference ID: `a_`-prefixed for an account-scoped integration, `c_`-prefixed when it is shared into a channel, `w_`-prefixed on legacy workspace-scoped integrations. + // Source reference ID, normally `a_` for an account-level integration; legacy `w_` references may also exist. Channel-owned `c_` integrations are excluded. RefID string `json:"ref_id" toon:"ref_id"` + // Stored type-specific configuration, including credential values without masking; matches the legacy `/datasource/*` response behavior. + Settings map[string]any `json:"settings" toon:"settings"` // Lifecycle status: `enabled` while the integration accepts events, `disabled` when it is paused. Status string `json:"status" toon:"status"` // ID of the team that owns the integration. `0` when it is not assigned to a team. TeamID int64 `json:"team_id" toon:"team_id"` // Unix timestamp in seconds when the integration was last updated. UpdatedAt Timestamp `json:"updated_at" toon:"updated_at"` + // ID of the member who last updated the integration. + UpdatedBy int64 `json:"updated_by" toon:"updated_by"` } // IntegrationLifecycleRequest is generated from the Flashduty OpenAPI schema. @@ -8213,7 +8265,7 @@ type RoleUpsertResponse struct { // RotateIntegrationKeyResponse is generated from the Flashduty OpenAPI schema. type RotateIntegrationKeyResponse struct { - // The new key. The previous key stops working immediately; this value cannot be read again later. + // New integration key; the previous key stops working immediately. Also readable from `/integration/list` and `/integration/info`. IntegrationKey string `json:"integration_key" toon:"integration_key"` } @@ -11546,9 +11598,11 @@ type UpdateIntegrationRequest struct { Description *string `json:"description,omitempty" toon:"description,omitempty"` // Integration ID. IntegrationID int64 `json:"integration_id" toon:"integration_id"` + // Run the plugin test when submitting `settings`; no test is performed for a metadata-only update. + IsTest bool `json:"is_test,omitempty" toon:"is_test,omitempty"` // New name, 2–49 characters. Name *string `json:"name,omitempty" toon:"name,omitempty"` - // Replacement configuration for the integration type. Sensitive entries left out, or sent back as the masked `******`, keep their stored value. + // Replacement type-specific configuration. Known sensitive fields omitted or sent as `******` keep their stored value; an explicit empty headers object clears headers. Settings map[string]any `json:"settings,omitempty" toon:"settings,omitempty"` // New owning team ID; `0` clears the team assignment. TeamID *int64 `json:"team_id,omitempty" toon:"team_id,omitempty"` @@ -12241,6 +12295,14 @@ type CreateInhibitRuleRequestTargetFiltersItemItem struct { Vals []string `json:"vals" toon:"vals"` } +// CreateIntegrationResponseTest is generated from the Flashduty OpenAPI schema. +type CreateIntegrationResponseTest struct { + // Test failure message; empty on success. + Message string `json:"message" toon:"message"` + // Whether the plugin test succeeded. + Success bool `json:"success" toon:"success"` +} + // CreateSilenceRuleRequestFiltersItemItem is generated from the Flashduty OpenAPI schema. type CreateSilenceRuleRequestFiltersItemItem struct { // Field key (e.g. `alert_severity`, `labels.service`). @@ -12388,6 +12450,14 @@ type IncidentRawItemRespondersItem struct { PersonName string `json:"person_name" toon:"person_name"` } +// IntegrationDetailTest is generated from the Flashduty OpenAPI schema. +type IntegrationDetailTest struct { + // Test failure message; empty on success. + Message string `json:"message" toon:"message"` + // Whether the plugin test succeeded. + Success bool `json:"success" toon:"success"` +} + // PostMortemItemBasics is generated from the Flashduty OpenAPI schema. type PostMortemItemBasics struct { // Earliest start time among linked incidents (seconds). diff --git a/openapi/openapi.en.json b/openapi/openapi.en.json index d7d57a3..f0d665b 100644 --- a/openapi/openapi.en.json +++ b/openapi/openapi.en.json @@ -32698,7 +32698,7 @@ }, "IntegrationItem": { "type": "object", - "description": "A configured integration. Timestamps are Unix epoch seconds.", + "description": "Public integration list item with full stored configuration. Timestamps are Unix epoch seconds.", "required": [ "integration_id", "team_id", @@ -32711,7 +32711,17 @@ "ref_id", "created_at", "updated_at", - "last_time" + "last_time", + "editable", + "creator_id", + "updated_by", + "no_editable", + "plugin_id", + "integration_key", + "settings", + "data_source_id", + "account_id", + "exclusive_data_source_id" ], "properties": { "integration_id": { @@ -32760,7 +32770,7 @@ }, "ref_id": { "type": "string", - "description": "Source reference ID: `a_`-prefixed for an account-scoped integration, `c_`-prefixed when it is shared into a channel, `w_`-prefixed on legacy workspace-scoped integrations." + "description": "Source reference ID, normally `a_` for an account-level integration; legacy `w_` references may also exist. Channel-owned `c_` integrations are excluded." }, "created_at": { "type": "integer", @@ -32776,6 +32786,53 @@ "type": "integer", "format": "int64", "description": "Unix timestamp in seconds of the most recent event received. `0` when no event has arrived yet." + }, + "editable": { + "type": "boolean", + "description": "Whether the current caller passes the integration object-permission check; write calls also require Integrations Manage." + }, + "creator_id": { + "type": "integer", + "format": "int64", + "description": "ID of the member who created the integration." + }, + "updated_by": { + "type": "integer", + "format": "int64", + "description": "ID of the member who last updated the integration." + }, + "no_editable": { + "type": "boolean", + "description": "Read-only flag. When true, the public APIs do not allow changes to this integration." + }, + "plugin_id": { + "type": "integer", + "format": "int64", + "description": "Internal integration type ID; use `plugin_type`, not this ID, when creating an integration." + }, + "integration_key": { + "type": "string", + "description": "Decrypted integration key for inbound pushes. Empty when no key is stored; distinct from the account `app_key`." + }, + "settings": { + "type": "object", + "additionalProperties": true, + "description": "Stored type-specific configuration, including credential values without masking; matches the legacy `/datasource/*` response behavior." + }, + "data_source_id": { + "type": "integer", + "format": "int64", + "description": "Legacy datasource ID, equal to `integration_id`." + }, + "account_id": { + "type": "integer", + "format": "int64", + "description": "ID of the account that owns the integration." + }, + "exclusive_data_source_id": { + "type": "integer", + "format": "int64", + "description": "Legacy compatibility field; `0` for items returned by this public-integration list." } } }, @@ -32829,7 +32886,7 @@ }, "IntegrationDetail": { "type": "object", - "description": "A single integration with its full settings. Timestamps are Unix epoch seconds.", + "description": "Public integration detail with stored settings and its decrypted key. Timestamps are Unix epoch seconds.", "required": [ "integration_id", "team_id", @@ -32843,112 +32900,128 @@ "created_at", "updated_at", "last_time", + "editable", + "creator_id", + "updated_by", + "no_editable", + "plugin_id", + "integration_key", "settings" ], "properties": { - "integration_id": [ - { - "type": "integer", - "format": "int64", - "description": "Integration ID." - }, - true - ], - "team_id": [ - { - "type": "integer", - "format": "int64", - "description": "ID of the team that owns the integration. `0` when it is not assigned to a team." - }, - true - ], - "plugin_type": [ - { - "type": "string", - "description": "Integration type, for example `standard.alert` or `zabbix.alert`." - }, - true - ], - "plugin_type_name": [ - { - "type": "string", - "description": "Display name of the integration type, in the language of the request." - }, - true - ], - "category": [ - { - "type": "string", - "enum": [ - "event.alert", - "event.change", - "im", - "webhook" - ], - "description": "Category the integration belongs to." - }, - true - ], - "name": [ - { - "type": "string", - "description": "Integration name." - }, - true - ], - "description": [ - { - "type": "string", - "description": "Free-form description." - }, - true - ], - "status": [ - { - "type": "string", - "enum": [ - "enabled", - "disabled" - ], - "description": "Lifecycle status: `enabled` while the integration accepts events, `disabled` when it is paused." - }, - true - ], - "ref_id": [ - { - "type": "string", - "description": "Source reference ID: `a_`-prefixed for an account-scoped integration, `c_`-prefixed when it is shared into a channel." - }, - true - ], - "created_at": [ - { - "type": "integer", - "format": "int64", - "description": "Unix timestamp in seconds when the integration was created." - }, - true - ], - "updated_at": [ - { - "type": "integer", - "format": "int64", - "description": "Unix timestamp in seconds when the integration was last updated." - }, - true - ], - "last_time": [ - { - "type": "integer", - "format": "int64", - "description": "Unix timestamp in seconds of the most recent event received. `0` when no event has arrived yet." - }, - true - ], + "integration_id": { + "type": "integer", + "format": "int64", + "description": "Integration ID." + }, + "team_id": { + "type": "integer", + "format": "int64", + "description": "ID of the team that owns the integration. `0` when it is not assigned to a team." + }, + "plugin_type": { + "type": "string", + "description": "Integration type, for example `standard.alert` or `zabbix.alert`." + }, + "plugin_type_name": { + "type": "string", + "description": "Display name of the integration type, in the language of the request." + }, + "category": { + "type": "string", + "enum": [ + "event.alert", + "event.change", + "im", + "webhook" + ], + "description": "Category the integration belongs to: `event.alert` alert events, `event.change` change events, `im` IM bots, `webhook` custom webhooks." + }, + "name": { + "type": "string", + "description": "Integration name." + }, + "description": { + "type": "string", + "description": "Free-form description." + }, + "status": { + "type": "string", + "enum": [ + "enabled", + "disabled" + ], + "description": "Lifecycle status: `enabled` while the integration accepts events, `disabled` when it is paused." + }, + "ref_id": { + "type": "string", + "description": "Source reference ID, normally `a_` for an account-level integration; legacy `w_` references may also exist. Channel-owned `c_` integrations are excluded." + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp in seconds when the integration was created." + }, + "updated_at": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp in seconds when the integration was last updated." + }, + "last_time": { + "type": "integer", + "format": "int64", + "description": "Unix timestamp in seconds of the most recent event received. `0` when no event has arrived yet." + }, + "editable": { + "type": "boolean", + "description": "Whether the current caller passes the integration object-permission check; write calls also require Integrations Manage." + }, + "creator_id": { + "type": "integer", + "format": "int64", + "description": "ID of the member who created the integration." + }, + "updated_by": { + "type": "integer", + "format": "int64", + "description": "ID of the member who last updated the integration." + }, + "no_editable": { + "type": "boolean", + "description": "Read-only flag. When true, the public APIs do not allow changes to this integration." + }, + "plugin_id": { + "type": "integer", + "format": "int64", + "description": "Internal integration type ID; use `plugin_type`, not this ID, when creating an integration." + }, + "integration_key": { + "type": "string", + "description": "Decrypted integration key for inbound pushes. Empty when no key is stored; distinct from the account `app_key`." + }, "settings": { "type": "object", "additionalProperties": true, - "description": "Type-specific configuration. Sensitive values (endpoint, headers, secrets, passwords) are returned masked as `******`." + "description": "Stored type-specific configuration, including credential values without masking; matches the legacy `/datasource/*` response behavior." + }, + "test": { + "type": "object", + "description": "Plugin test result, returned only when a test is performed.", + "x-flashduty-preserve-absence": true, + "required": [ + "success", + "message" + ], + "properties": { + "success": { + "type": "boolean", + "description": "Whether the plugin test succeeded." + }, + "message": { + "type": "string", + "description": "Test failure message; empty on success." + } + } } } }, @@ -32968,7 +33041,7 @@ "type": "string", "minLength": 2, "maxLength": 49, - "description": "Integration name. 2–49 characters.", + "description": "Integration name, 2–49 characters when nonempty; defaults to the integration type name when omitted or empty.", "example": "Prod metrics alerts" }, "description": { @@ -32979,14 +33052,24 @@ "team_id": { "type": "integer", "format": "int64", - "minimum": 1, - "description": "Owning team ID.", + "minimum": 0, + "description": "Owning team ID; omit or use `0` for no team. A nonzero ID must belong to the current account.", "example": 1467226103121 }, "settings": { "type": "object", "description": "Type-specific configuration; the accepted keys depend on `plugin_type`.", "additionalProperties": true + }, + "integration_key": { + "type": "string", + "minLength": 35, + "maxLength": 35, + "description": "Optional pre-generated key: 35 characters, ending with the account ID modulo 1000 padded to three digits; it must not already be in use. Omit to generate a key." + }, + "is_test": { + "type": "boolean", + "description": "Run the plugin test during creation; false when omitted. Inspect the returned `test.success` for the test outcome." } } }, @@ -33006,8 +33089,27 @@ }, "integration_key": { "type": "string", - "description": "Key used to authenticate inbound pushes to this integration. Returned here only; fetch a new one with `POST /integration/key/rotate`.", - "example": "9f2c7d1b45a86e30f7b1c9d4a2e65738123" + "description": "Integration key for inbound pushes; also readable from `/integration/list` and `/integration/info`.", + "example": "0123456789abcdef0123456789abcdef100" + }, + "test": { + "type": "object", + "description": "Plugin test result, returned only when a test is performed.", + "x-flashduty-preserve-absence": true, + "required": [ + "success", + "message" + ], + "properties": { + "success": { + "type": "boolean", + "description": "Whether the plugin test succeeded." + }, + "message": { + "type": "string", + "description": "Test failure message; empty on success." + } + } } } }, @@ -33052,12 +33154,13 @@ "description": "New owning team ID; `0` clears the team assignment." }, "settings": { - "type": [ - "object", - "null" - ], - "description": "Replacement configuration for the integration type. Sensitive entries left out, or sent back as the masked `******`, keep their stored value.", + "type": "object", + "description": "Replacement type-specific configuration. Known sensitive fields omitted or sent as `******` keep their stored value; an explicit empty headers object clears headers.", "additionalProperties": true + }, + "is_test": { + "type": "boolean", + "description": "Run the plugin test when submitting `settings`; no test is performed for a metadata-only update." } } }, @@ -33086,8 +33189,8 @@ "properties": { "integration_key": { "type": "string", - "description": "The new key. The previous key stops working immediately; this value cannot be read again later.", - "example": "9f2c7d1b45a86e30f7b1c9d4a2e65738123" + "description": "New integration key; the previous key stops working immediately. Also readable from `/integration/list` and `/integration/info`.", + "example": "0123456789abcdef0123456789abcdef100" } } } @@ -67100,12 +67203,12 @@ "post": { "operationId": "integration-api-read-list", "summary": "List integrations", - "description": "List the account's alert-source and change-source integrations.", + "description": "List public integrations with their stored settings, decrypted keys, and edit permissions.", "tags": [ "On-call/Integrations" ], "x-mint": { - "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **1,000 requests/minute**; **50 requests/second** per account |\n| Permissions | **Integrations Read** (`on-call`) |\n\n## Usage\n\n- `orderby` defaults to `created_at`; `plugin_type` is sorted by the underlying plugin.\n- `plugin_type`, `status` and `category` accept comma-separated values; `ref_ids` values must start with `c_`, `a_` or `w_`.\n- With `is_my_team` set, results are limited to your teams and `team_ids` narrows that set further.", + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **1,000 requests/minute**; **50 requests/second** per account |\n| Permissions | **Integrations Read** (`on-call`) |\n\n## Usage\n\n- `orderby` defaults to `created_at`; `plugin_type` is sorted by the underlying plugin.\n- `plugin_type`, `status` and `category` accept comma-separated values; `ref_ids` values must start with `c_`, `a_` or `w_`; a `c_` filter is syntactically accepted but does not return channel-owned integrations.\n- With `is_my_team` set, results are limited to your teams and `team_ids` narrows that set further.\n- These APIs manage public (account-level) integrations only, not channel-owned dedicated integrations.\n- Items include `settings`, `integration_key`, `plugin_id`, `account_id`, and the legacy `data_source_id` alias; credential values are not masked.", "href": "/en/api-reference/on-call/integrations/integration-api-read-list", "metadata": { "sidebarTitle": "List integrations" @@ -67147,10 +67250,23 @@ "name": "Prod metrics alerts", "description": "Alerts pushed by the production Prometheus stack", "status": "enabled", - "ref_id": "a_6612f0c9e1b7a4d3508f2c6b", + "ref_id": "a_100", "created_at": 1775116800, "updated_at": 1775203200, - "last_time": 1775298600 + "last_time": 1775298600, + "editable": true, + "creator_id": 1467226103999, + "updated_by": 1467226103999, + "no_editable": false, + "plugin_id": 9, + "integration_key": "0123456789abcdef0123456789abcdef100", + "settings": { + "field_mapping": {}, + "value_mapping": {} + }, + "data_source_id": 6113996590131, + "account_id": 100, + "exclusive_data_source_id": 0 } ] } @@ -67192,12 +67308,12 @@ "post": { "operationId": "integration-api-read-info", "summary": "Get integration detail", - "description": "Return one integration, including its settings with sensitive values masked.", + "description": "Get a public integration with its stored settings and decrypted integration key.", "tags": [ "On-call/Integrations" ], "x-mint": { - "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **1,000 requests/minute**; **50 requests/second** per account |\n| Permissions | **Integrations Read** (`on-call`) |\n\n## Usage\n\n- Sensitive settings (endpoint, headers, secrets, passwords) come back masked as `******`.", + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **1,000 requests/minute**; **50 requests/second** per account |\n| Permissions | **Integrations Read** (`on-call`) |\n\n## Usage\n\n- These APIs manage public (account-level) integrations only, not channel-owned dedicated integrations.\n- Settings and the integration key are returned without masking, matching the legacy datasource detail response.", "href": "/en/api-reference/on-call/integrations/integration-api-read-info", "metadata": { "sidebarTitle": "Get integration detail" @@ -67234,20 +67350,20 @@ "name": "Prod metrics alerts", "description": "Alerts pushed by the production Prometheus stack", "status": "enabled", - "ref_id": "a_6612f0c9e1b7a4d3508f2c6b", + "ref_id": "a_100", "created_at": 1775116800, "updated_at": 1775203200, "last_time": 1775298600, "settings": { - "severity_mapping": [ - { - "P1": "Critical" - } - ], - "labels": { - "source": "prometheus" - } - } + "field_mapping": {}, + "value_mapping": {} + }, + "editable": true, + "creator_id": 1467226103999, + "updated_by": 1467226103999, + "no_editable": false, + "plugin_id": 9, + "integration_key": "0123456789abcdef0123456789abcdef100" } } } @@ -67285,12 +67401,12 @@ "post": { "operationId": "integration-api-write-create", "summary": "Create integration", - "description": "Create an integration for an alert source or change source.", + "description": "Create a public integration for a supported alert, change, IM, or webhook type.", "tags": [ "On-call/Integrations" ], "x-mint": { - "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **300 requests/minute**; **20 requests/second** per account |\n| Permissions | **Integrations Manage** (`on-call`) |\n\n## Usage\n\n- `plugin_type` must be one of the types listed by `POST /integration/type/list` with `supports_api_create: true`; anything else returns `InvalidParameter`.\n- `integration_key` is returned once. Store it at creation time — it is not part of `POST /integration/info`.\n- Every call is recorded in the account audit log. Don't put secrets in request fields.", + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **300 requests/minute**; **20 requests/second** per account |\n| Permissions | **Integrations Manage** (`on-call`) |\n\n## Usage\n\n- `plugin_type` must be one of the types listed by `POST /integration/type/list` with `supports_api_create: true`; anything else returns `InvalidParameter`.\n- These APIs manage public (account-level) integrations only, not channel-owned dedicated integrations.\n- An omitted `integration_key` is generated automatically; a supplied key must satisfy the length, account suffix, and uniqueness checks.\n- With `is_test: true`, inspect `test.success`; an HTTP 200 response alone does not establish that the plugin test succeeded.\n- Every call is recorded in the account audit log.", "href": "/en/api-reference/on-call/integrations/integration-api-write-create", "metadata": { "sidebarTitle": "Create integration" @@ -67320,7 +67436,11 @@ "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", "data": { "integration_id": 6113996590131, - "integration_key": "9f2c7d1b45a86e30f7b1c9d4a2e65738123" + "integration_key": "0123456789abcdef0123456789abcdef100", + "test": { + "success": true, + "message": "" + } } } } @@ -67352,12 +67472,10 @@ "description": "Alerts pushed by the production Prometheus stack", "team_id": 1467226103121, "settings": { - "severity_mapping": [ - { - "P1": "Critical" - } - ] - } + "field_mapping": {}, + "value_mapping": {} + }, + "is_test": true } } } @@ -67373,7 +67491,7 @@ "On-call/Integrations" ], "x-mint": { - "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **300 requests/minute**; **20 requests/second** per account |\n| Permissions | **Integrations Manage** (`on-call`) |\n\n## Usage\n\n- Only the fields you send are changed; omitted fields keep their current value.\n- `settings` replaces the stored configuration. Sensitive entries left out, or sent back as the masked `******`, keep their stored value.\n- Every call is recorded in the account audit log. Don't put secrets in request fields.", + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **300 requests/minute**; **20 requests/second** per account |\n| Permissions | **Integrations Manage** (`on-call`) |\n\n## Usage\n\n- Only the fields you send are changed; omitted fields keep their current value.\n- Submitting `settings` replaces the configuration; known sensitive fields omitted or sent as `******` retain their value. Send `headers: {}` to clear headers; use the integration type’s explicit empty value to clear a body.\n- Submit `settings` together with `is_test: true` to receive a plugin test result; settings in the response are not masked.\n- These APIs manage public (account-level) integrations only, not channel-owned dedicated integrations.\n- Write access also requires object permission: the account owner, an administrator, the creator, or a member of the assigned team; with no team, members with Integrations Manage may operate. Read-only integrations cannot be changed.\n- Every call is recorded in the account audit log.", "href": "/en/api-reference/on-call/integrations/integration-api-write-update", "metadata": { "sidebarTitle": "Update integration" @@ -67410,19 +67528,23 @@ "name": "Prod metrics alerts (primary)", "description": "Alerts pushed by the production Prometheus stack", "status": "enabled", - "ref_id": "a_6612f0c9e1b7a4d3508f2c6b", + "ref_id": "a_100", "created_at": 1775116800, "updated_at": 1775298600, "last_time": 1775298600, "settings": { - "severity_mapping": [ - { - "P1": "Critical" - } - ], - "labels": { - "source": "prometheus" - } + "field_mapping": {}, + "value_mapping": {} + }, + "editable": true, + "creator_id": 1467226103999, + "updated_by": 1467226103999, + "no_editable": false, + "plugin_id": 9, + "integration_key": "0123456789abcdef0123456789abcdef100", + "test": { + "success": true, + "message": "" } } } @@ -67452,7 +67574,12 @@ "example": { "integration_id": 6113996590131, "name": "Prod metrics alerts (primary)", - "team_id": 1467226103121 + "team_id": 1467226103121, + "settings": { + "field_mapping": {}, + "value_mapping": {} + }, + "is_test": true } } } @@ -67468,7 +67595,7 @@ "On-call/Integrations" ], "x-mint": { - "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **300 requests/minute**; **20 requests/second** per account |\n| Permissions | **Integrations Manage** (`on-call`) |\n\n## Usage\n\n- Deleting an integration that other objects still reference returns HTTP 400 with `error.code` = `ReferenceExist` and `data.refs` listing them.\n- Every call is recorded in the account audit log. Don't put secrets in request fields.", + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **300 requests/minute**; **20 requests/second** per account |\n| Permissions | **Integrations Manage** (`on-call`) |\n\n## Usage\n\n- Deleting an integration that other objects still reference returns HTTP 400 with `error.code` = `ReferenceExist` and `data.refs` listing them.\n- Every call is recorded in the account audit log.\n- These APIs manage public (account-level) integrations only, not channel-owned dedicated integrations.\n- Write access also requires object permission: the account owner, an administrator, the creator, or a member of the assigned team; with no team, members with Integrations Manage may operate. Read-only integrations cannot be changed.", "href": "/en/api-reference/on-call/integrations/integration-api-write-delete", "metadata": { "sidebarTitle": "Delete integration" @@ -67538,7 +67665,7 @@ "On-call/Integrations" ], "x-mint": { - "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **300 requests/minute**; **20 requests/second** per account |\n| Permissions | **Integrations Manage** (`on-call`) |\n\n## Usage\n\n- Enabling an integration resumes its scheduled pull task and heartbeat, and requires the integration type to be available on the current plan.\n- Every call is recorded in the account audit log. Don't put secrets in request fields.", + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **300 requests/minute**; **20 requests/second** per account |\n| Permissions | **Integrations Manage** (`on-call`) |\n\n## Usage\n\n- Enabling an integration resumes its scheduled pull task and heartbeat, and requires the integration type to be available on the current plan.\n- Every call is recorded in the account audit log.\n- These APIs manage public (account-level) integrations only, not channel-owned dedicated integrations.\n- Write access also requires object permission: the account owner, an administrator, the creator, or a member of the assigned team; with no team, members with Integrations Manage may operate. Read-only integrations cannot be changed.", "href": "/en/api-reference/on-call/integrations/integration-api-write-enable", "metadata": { "sidebarTitle": "Enable integration" @@ -67608,7 +67735,7 @@ "On-call/Integrations" ], "x-mint": { - "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **300 requests/minute**; **20 requests/second** per account |\n| Permissions | **Integrations Manage** (`on-call`) |\n\n## Usage\n\n- Disabling stops event ingestion and pauses the scheduled pull task; settings are kept so the integration can be enabled again.\n- Every call is recorded in the account audit log. Don't put secrets in request fields.", + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **300 requests/minute**; **20 requests/second** per account |\n| Permissions | **Integrations Manage** (`on-call`) |\n\n## Usage\n\n- Disabling stops event ingestion and pauses the scheduled pull task; settings are kept so the integration can be enabled again.\n- Every call is recorded in the account audit log.\n- These APIs manage public (account-level) integrations only, not channel-owned dedicated integrations.\n- Write access also requires object permission: the account owner, an administrator, the creator, or a member of the assigned team; with no team, members with Integrations Manage may operate. Read-only integrations cannot be changed.", "href": "/en/api-reference/on-call/integrations/integration-api-write-disable", "metadata": { "sidebarTitle": "Disable integration" @@ -67678,7 +67805,7 @@ "On-call/Integrations" ], "x-mint": { - "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **300 requests/minute**; **20 requests/second** per account |\n| Permissions | **Integrations Manage** (`on-call`) |\n\n## Usage\n\n- The previous key stops working immediately, so update the pushing side before rotating.\n- The new key is returned once and cannot be read again later.\n- Every call is recorded in the account audit log. Don't put secrets in request fields.", + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **300 requests/minute**; **20 requests/second** per account |\n| Permissions | **Integrations Manage** (`on-call`) |\n\n## Usage\n\n- The previous key stops working immediately; after rotation, update the pushing side with the new key.\n- The new key can also be read from `/integration/list` and `/integration/info`.\n- These APIs manage public (account-level) integrations only, not channel-owned dedicated integrations.\n- Write access also requires object permission: the account owner, an administrator, the creator, or a member of the assigned team; with no team, members with Integrations Manage may operate. Read-only integrations cannot be changed.\n- Every call is recorded in the account audit log.", "href": "/en/api-reference/on-call/integrations/integration-api-write-rotate-key", "metadata": { "sidebarTitle": "Rotate integration key" @@ -67707,7 +67834,7 @@ "example": { "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", "data": { - "integration_key": "9f2c7d1b45a86e30f7b1c9d4a2e65738123" + "integration_key": "0123456789abcdef0123456789abcdef100" } } } diff --git a/openapi/openapi.zh.json b/openapi/openapi.zh.json index 171481a..4e5322f 100644 --- a/openapi/openapi.zh.json +++ b/openapi/openapi.zh.json @@ -32698,7 +32698,7 @@ }, "IntegrationItem": { "type": "object", - "description": "已配置的集成;时间字段为 Unix 时间戳(秒)。", + "description": "公共集成列表项,包含完整存储配置;时间字段为 Unix 时间戳(秒)。", "required": [ "integration_id", "team_id", @@ -32711,7 +32711,17 @@ "ref_id", "created_at", "updated_at", - "last_time" + "last_time", + "editable", + "creator_id", + "updated_by", + "no_editable", + "plugin_id", + "integration_key", + "settings", + "data_source_id", + "account_id", + "exclusive_data_source_id" ], "properties": { "integration_id": { @@ -32760,7 +32770,7 @@ }, "ref_id": { "type": "string", - "description": "来源引用 ID:`a_` 开头表示账户级集成,集成被共享到协作空间时为 `c_` 开头,旧版工作空间级集成为 `w_` 开头。" + "description": "来源引用 ID,账户级集成通常为 `a_`,旧记录也可能使用 `w_`;本接口不返回 `c_` 开头的专属集成。" }, "created_at": { "type": "integer", @@ -32776,6 +32786,53 @@ "type": "integer", "format": "int64", "description": "最近收到事件时间的 Unix 时间戳(秒),尚未收到事件时为 `0`。" + }, + "editable": { + "type": "boolean", + "description": "当前调用者是否通过集成对象权限校验;执行写操作还需集成中心管理权限。" + }, + "creator_id": { + "type": "integer", + "format": "int64", + "description": "创建集成的成员 ID。" + }, + "updated_by": { + "type": "integer", + "format": "int64", + "description": "最近更新集成的成员 ID。" + }, + "no_editable": { + "type": "boolean", + "description": "只读标记;为 true 时公开接口不允许修改该集成。" + }, + "plugin_id": { + "type": "integer", + "format": "int64", + "description": "集成类型的内部 ID;创建集成使用 `plugin_type`,不使用此 ID。" + }, + "integration_key": { + "type": "string", + "description": "解密后的集成 Key,用于事件推送;未存储 Key 时为空字符串,与账户 `app_key` 不同。" + }, + "settings": { + "type": "object", + "additionalProperties": true, + "description": "按存储值返回的类型专属配置,凭据不打码,与旧 `/datasource/*` 返回行为一致。" + }, + "data_source_id": { + "type": "integer", + "format": "int64", + "description": "旧 datasource ID,与 `integration_id` 相同。" + }, + "account_id": { + "type": "integer", + "format": "int64", + "description": "集成所属账户 ID。" + }, + "exclusive_data_source_id": { + "type": "integer", + "format": "int64", + "description": "旧接口兼容字段;此公共集成列表返回的值为 `0`。" } } }, @@ -32829,7 +32886,7 @@ }, "IntegrationDetail": { "type": "object", - "description": "单个集成详情,含完整配置;时间字段为 Unix 时间戳(秒)。", + "description": "公共集成详情,包含存储配置与解密 Key;时间字段为 Unix 时间戳(秒)。", "required": [ "integration_id", "team_id", @@ -32843,112 +32900,128 @@ "created_at", "updated_at", "last_time", + "editable", + "creator_id", + "updated_by", + "no_editable", + "plugin_id", + "integration_key", "settings" ], "properties": { - "integration_id": [ - { - "type": "integer", - "format": "int64", - "description": "集成 ID。" - }, - true - ], - "team_id": [ - { - "type": "integer", - "format": "int64", - "description": "所属团队 ID;未归属团队时为 `0`。" - }, - true - ], - "plugin_type": [ - { - "type": "string", - "description": "集成类型,例如 `standard.alert`、`zabbix.alert`。" - }, - true - ], - "plugin_type_name": [ - { - "type": "string", - "description": "集成类型显示名,随请求语言变化。" - }, - true - ], - "category": [ - { - "type": "string", - "enum": [ - "event.alert", - "event.change", - "im", - "webhook" - ], - "description": "集成所属分类。" - }, - true - ], - "name": [ - { - "type": "string", - "description": "集成名称。" - }, - true - ], - "description": [ - { - "type": "string", - "description": "集成描述。" - }, - true - ], - "status": [ - { - "type": "string", - "enum": [ - "enabled", - "disabled" - ], - "description": "状态:`enabled` 正常接收事件,`disabled` 已暂停。" - }, - true - ], - "ref_id": [ - { - "type": "string", - "description": "来源引用 ID:`a_` 开头表示账户级集成,集成被共享到协作空间时为 `c_` 开头。" - }, - true - ], - "created_at": [ - { - "type": "integer", - "format": "int64", - "description": "创建时间的 Unix 时间戳(秒)。" - }, - true - ], - "updated_at": [ - { - "type": "integer", - "format": "int64", - "description": "最近更新时间的 Unix 时间戳(秒)。" - }, - true - ], - "last_time": [ - { - "type": "integer", - "format": "int64", - "description": "最近收到事件时间的 Unix 时间戳(秒),尚未收到事件时为 `0`。" - }, - true - ], + "integration_id": { + "type": "integer", + "format": "int64", + "description": "集成 ID。" + }, + "team_id": { + "type": "integer", + "format": "int64", + "description": "所属团队 ID;未归属团队时为 `0`。" + }, + "plugin_type": { + "type": "string", + "description": "集成类型,例如 `standard.alert`、`zabbix.alert`。" + }, + "plugin_type_name": { + "type": "string", + "description": "集成类型显示名,随请求语言变化。" + }, + "category": { + "type": "string", + "enum": [ + "event.alert", + "event.change", + "im", + "webhook" + ], + "description": "集成所属分类:`event.alert` 告警事件,`event.change` 变更事件,`im` IM 机器人,`webhook` 自定义 Webhook。" + }, + "name": { + "type": "string", + "description": "集成名称。" + }, + "description": { + "type": "string", + "description": "集成描述。" + }, + "status": { + "type": "string", + "enum": [ + "enabled", + "disabled" + ], + "description": "状态:`enabled` 正常接收事件,`disabled` 已暂停。" + }, + "ref_id": { + "type": "string", + "description": "来源引用 ID,账户级集成通常为 `a_`,旧记录也可能使用 `w_`;本接口不返回 `c_` 开头的专属集成。" + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "创建时间的 Unix 时间戳(秒)。" + }, + "updated_at": { + "type": "integer", + "format": "int64", + "description": "最近更新时间的 Unix 时间戳(秒)。" + }, + "last_time": { + "type": "integer", + "format": "int64", + "description": "最近收到事件时间的 Unix 时间戳(秒),尚未收到事件时为 `0`。" + }, + "editable": { + "type": "boolean", + "description": "当前调用者是否通过集成对象权限校验;执行写操作还需集成中心管理权限。" + }, + "creator_id": { + "type": "integer", + "format": "int64", + "description": "创建集成的成员 ID。" + }, + "updated_by": { + "type": "integer", + "format": "int64", + "description": "最近更新集成的成员 ID。" + }, + "no_editable": { + "type": "boolean", + "description": "只读标记;为 true 时公开接口不允许修改该集成。" + }, + "plugin_id": { + "type": "integer", + "format": "int64", + "description": "集成类型的内部 ID;创建集成使用 `plugin_type`,不使用此 ID。" + }, + "integration_key": { + "type": "string", + "description": "解密后的集成 Key,用于事件推送;未存储 Key 时为空字符串,与账户 `app_key` 不同。" + }, "settings": { "type": "object", "additionalProperties": true, - "description": "集成类型专属配置;敏感字段(endpoint、headers、密钥、密码)以 `******` 打码返回。" + "description": "按存储值返回的类型专属配置,凭据不打码,与旧 `/datasource/*` 返回行为一致。" + }, + "test": { + "type": "object", + "description": "执行插件测试时返回的测试结果。", + "x-flashduty-preserve-absence": true, + "required": [ + "success", + "message" + ], + "properties": { + "success": { + "type": "boolean", + "description": "插件测试是否成功。" + }, + "message": { + "type": "string", + "description": "测试失败信息;成功时为空字符串。" + } + } } } }, @@ -32968,7 +33041,7 @@ "type": "string", "minLength": 2, "maxLength": 49, - "description": "集成名称,2–49 个字符。", + "description": "集成名称,非空时为 2–49 字符;不传或为空时使用集成类型名称。", "example": "Prod metrics alerts" }, "description": { @@ -32979,14 +33052,24 @@ "team_id": { "type": "integer", "format": "int64", - "minimum": 1, - "description": "所属团队 ID。", + "minimum": 0, + "description": "所属团队 ID;不传或传 `0` 表示不设置团队,非零 ID 必须属于当前账户。", "example": 1467226103121 }, "settings": { "type": "object", "description": "集成类型专属配置,可传字段取决于 `plugin_type`。", "additionalProperties": true + }, + "integration_key": { + "type": "string", + "minLength": 35, + "maxLength": 35, + "description": "可选预生成 Key,长度 35,末尾为账户 ID 对 1000 取余后补零至三位,且不能已被使用;不传时自动生成。" + }, + "is_test": { + "type": "boolean", + "description": "创建时执行插件测试;不传时为 false,测试结果查看返回的 `test.success`。" } } }, @@ -33006,8 +33089,27 @@ }, "integration_key": { "type": "string", - "description": "该集成的推送鉴权 Key,仅在此处返回;需要新 Key 请调用 `POST /integration/key/rotate`。", - "example": "9f2c7d1b45a86e30f7b1c9d4a2e65738123" + "description": "用于事件推送的集成 Key,也可通过 `/integration/list` 和 `/integration/info` 查询。", + "example": "0123456789abcdef0123456789abcdef100" + }, + "test": { + "type": "object", + "description": "执行插件测试时返回的测试结果。", + "x-flashduty-preserve-absence": true, + "required": [ + "success", + "message" + ], + "properties": { + "success": { + "type": "boolean", + "description": "插件测试是否成功。" + }, + "message": { + "type": "string", + "description": "测试失败信息;成功时为空字符串。" + } + } } } }, @@ -33052,12 +33154,13 @@ "description": "新的所属团队 ID;传 `0` 表示清除团队归属。" }, "settings": { - "type": [ - "object", - "null" - ], - "description": "替换该集成类型的配置;敏感字段不传、或原样回传 `******`,都会保留已存储的值。", + "type": "object", + "description": "替换类型专属配置;已识别的敏感字段不传或传 `******` 时保留原值,显式传空 headers 对象可清空 Headers。", "additionalProperties": true + }, + "is_test": { + "type": "boolean", + "description": "提交 `settings` 时执行插件测试;仅更新名称、描述或团队时不执行测试。" } } }, @@ -33086,8 +33189,8 @@ "properties": { "integration_key": { "type": "string", - "description": "新的集成 Key。旧 Key 立即失效,且该值之后无法再次读取。", - "example": "9f2c7d1b45a86e30f7b1c9d4a2e65738123" + "description": "新的集成 Key,旧 Key 立即失效;新 Key 也可通过 `/integration/list` 和 `/integration/info` 查询。", + "example": "0123456789abcdef0123456789abcdef100" } } } @@ -67100,12 +67203,12 @@ "post": { "operationId": "integration-api-read-list", "summary": "查询集成列表", - "description": "查询当前账户的告警源与变更源集成列表。", + "description": "查询公共集成列表,返回存储配置、解密 Key 与当前调用者的编辑权限。", "tags": [ "On-call/集成中心" ], "x-mint": { - "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **1,000 次/分钟**;**50 次/秒** |\n| 权限要求 | **集成中心查看**(`on-call`) |\n\n## 使用说明\n\n- `orderby` 默认 `created_at`;`plugin_type` 按底层插件排序。\n- `plugin_type`、`status`、`category` 支持逗号分隔多值;`ref_ids` 每个值必须以 `c_`、`a_` 或 `w_` 开头。\n- 传入 `is_my_team` 时仅返回你所在团队的集成,`team_ids` 会在该范围内进一步收窄。", + "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **1,000 次/分钟**;**50 次/秒** |\n| 权限要求 | **集成中心查看**(`on-call`) |\n\n## 使用说明\n\n- `orderby` 默认 `created_at`;`plugin_type` 按底层插件排序。\n- `plugin_type`、`status`、`category` 支持逗号分隔多值;`ref_ids` 每个值必须以 `c_`、`a_` 或 `w_` 开头;`c_` 过滤值通过格式校验,但不会返回协作空间专属集成。\n- 传入 `is_my_team` 时仅返回你所在团队的集成,`team_ids` 会在该范围内进一步收窄。\n- 这些接口仅管理公共(账户级)集成,不管理协作空间专属集成。\n- 列表项包含 `settings`、`integration_key`、`plugin_id`、`account_id` 及旧 ID 别名 `data_source_id`,凭据按原值返回,不打码。", "href": "/zh/api-reference/on-call/integrations/integration-api-read-list", "metadata": { "sidebarTitle": "查询集成列表" @@ -67147,10 +67250,23 @@ "name": "Prod metrics alerts", "description": "Alerts pushed by the production Prometheus stack", "status": "enabled", - "ref_id": "a_6612f0c9e1b7a4d3508f2c6b", + "ref_id": "a_100", "created_at": 1775116800, "updated_at": 1775203200, - "last_time": 1775298600 + "last_time": 1775298600, + "editable": true, + "creator_id": 1467226103999, + "updated_by": 1467226103999, + "no_editable": false, + "plugin_id": 9, + "integration_key": "0123456789abcdef0123456789abcdef100", + "settings": { + "field_mapping": {}, + "value_mapping": {} + }, + "data_source_id": 6113996590131, + "account_id": 100, + "exclusive_data_source_id": 0 } ] } @@ -67192,12 +67308,12 @@ "post": { "operationId": "integration-api-read-info", "summary": "查询集成详情", - "description": "查询单个集成详情,敏感配置以打码形式返回。", + "description": "查询单个公共集成详情,返回存储配置及解密后的集成 Key。", "tags": [ "On-call/集成中心" ], "x-mint": { - "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **1,000 次/分钟**;**50 次/秒** |\n| 权限要求 | **集成中心查看**(`on-call`) |\n\n## 使用说明\n\n- 敏感配置(endpoint、headers、密钥、密码)均以 `******` 打码返回。", + "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **1,000 次/分钟**;**50 次/秒** |\n| 权限要求 | **集成中心查看**(`on-call`) |\n\n## 使用说明\n\n- 这些接口仅管理公共(账户级)集成,不管理协作空间专属集成。\n- settings 与集成 Key 按原值返回,不打码,与旧 datasource 详情接口行为一致。", "href": "/zh/api-reference/on-call/integrations/integration-api-read-info", "metadata": { "sidebarTitle": "查询集成详情" @@ -67234,20 +67350,20 @@ "name": "Prod metrics alerts", "description": "Alerts pushed by the production Prometheus stack", "status": "enabled", - "ref_id": "a_6612f0c9e1b7a4d3508f2c6b", + "ref_id": "a_100", "created_at": 1775116800, "updated_at": 1775203200, "last_time": 1775298600, "settings": { - "severity_mapping": [ - { - "P1": "Critical" - } - ], - "labels": { - "source": "prometheus" - } - } + "field_mapping": {}, + "value_mapping": {} + }, + "editable": true, + "creator_id": 1467226103999, + "updated_by": 1467226103999, + "no_editable": false, + "plugin_id": 9, + "integration_key": "0123456789abcdef0123456789abcdef100" } } } @@ -67285,12 +67401,12 @@ "post": { "operationId": "integration-api-write-create", "summary": "创建集成", - "description": "为某个告警源或变更源创建集成。", + "description": "创建支持的告警源、变更源、IM 或 Webhook 类型公共集成。", "tags": [ "On-call/集成中心" ], "x-mint": { - "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **300 次/分钟**;**20 次/秒** |\n| 权限要求 | **集成中心管理**(`on-call`) |\n\n## 使用说明\n\n- `plugin_type` 必须是 `POST /integration/type/list` 中 `supports_api_create: true` 的类型,否则返回 `InvalidParameter`。\n- `integration_key` 仅在创建时返回一次,请及时保存;`POST /integration/info` 不会返回该字段。\n- 每次调用都会记录到账户审计日志,请不要把敏感信息放在请求字段中。", + "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **300 次/分钟**;**20 次/秒** |\n| 权限要求 | **集成中心管理**(`on-call`) |\n\n## 使用说明\n\n- `plugin_type` 必须是 `POST /integration/type/list` 中 `supports_api_create: true` 的类型,否则返回 `InvalidParameter`。\n- 这些接口仅管理公共(账户级)集成,不管理协作空间专属集成。\n- 不传 `integration_key` 时自动生成;传入时须满足长度、账户后缀与唯一性校验。\n- 传 `is_test: true` 时请检查 `test.success`,不能只根据 HTTP 200 判断插件测试成功。\n- 每次调用都会记录到账户审计日志。", "href": "/zh/api-reference/on-call/integrations/integration-api-write-create", "metadata": { "sidebarTitle": "创建集成" @@ -67320,7 +67436,11 @@ "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", "data": { "integration_id": 6113996590131, - "integration_key": "9f2c7d1b45a86e30f7b1c9d4a2e65738123" + "integration_key": "0123456789abcdef0123456789abcdef100", + "test": { + "success": true, + "message": "" + } } } } @@ -67352,12 +67472,10 @@ "description": "Alerts pushed by the production Prometheus stack", "team_id": 1467226103121, "settings": { - "severity_mapping": [ - { - "P1": "Critical" - } - ] - } + "field_mapping": {}, + "value_mapping": {} + }, + "is_test": true } } } @@ -67373,7 +67491,7 @@ "On-call/集成中心" ], "x-mint": { - "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **300 次/分钟**;**20 次/秒** |\n| 权限要求 | **集成中心管理**(`on-call`) |\n\n## 使用说明\n\n- 仅发送的字段会被修改,未发送的字段保持原值。\n- `settings` 会整体替换已存储的配置;敏感字段不传、或原样回传打码值 `******`,都会保留已存储的值。\n- 每次调用都会记录到账户审计日志,请不要把敏感信息放在请求字段中。", + "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **300 次/分钟**;**20 次/秒** |\n| 权限要求 | **集成中心管理**(`on-call`) |\n\n## 使用说明\n\n- 仅发送的字段会被修改,未发送的字段保持原值。\n- 提交 `settings` 会替换配置;已识别的敏感字段不传或传 `******` 时保留原值。清空 Headers 请传 `headers: {}`;清空 Body 请使用该集成类型的显式空值。\n- 同时提交 `settings` 与 `is_test: true` 可获得插件测试结果,返回的 settings 不打码。\n- 这些接口仅管理公共(账户级)集成,不管理协作空间专属集成。\n- 写操作还需对象权限:主体账户、管理员、创建者或所属团队成员可操作;未设置团队时,拥有集成中心管理权限的成员可操作,只读集成不可修改。\n- 每次调用都会记录到账户审计日志。", "href": "/zh/api-reference/on-call/integrations/integration-api-write-update", "metadata": { "sidebarTitle": "更新集成" @@ -67410,19 +67528,23 @@ "name": "Prod metrics alerts (primary)", "description": "Alerts pushed by the production Prometheus stack", "status": "enabled", - "ref_id": "a_6612f0c9e1b7a4d3508f2c6b", + "ref_id": "a_100", "created_at": 1775116800, "updated_at": 1775298600, "last_time": 1775298600, "settings": { - "severity_mapping": [ - { - "P1": "Critical" - } - ], - "labels": { - "source": "prometheus" - } + "field_mapping": {}, + "value_mapping": {} + }, + "editable": true, + "creator_id": 1467226103999, + "updated_by": 1467226103999, + "no_editable": false, + "plugin_id": 9, + "integration_key": "0123456789abcdef0123456789abcdef100", + "test": { + "success": true, + "message": "" } } } @@ -67452,7 +67574,12 @@ "example": { "integration_id": 6113996590131, "name": "Prod metrics alerts (primary)", - "team_id": 1467226103121 + "team_id": 1467226103121, + "settings": { + "field_mapping": {}, + "value_mapping": {} + }, + "is_test": true } } } @@ -67468,7 +67595,7 @@ "On-call/集成中心" ], "x-mint": { - "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **300 次/分钟**;**20 次/秒** |\n| 权限要求 | **集成中心管理**(`on-call`) |\n\n## 使用说明\n\n- 若该集成仍被其他对象引用,删除会返回 HTTP 400,`error.code` 为 `ReferenceExist`,`data.refs` 列出引用方。\n- 每次调用都会记录到账户审计日志,请不要把敏感信息放在请求字段中。", + "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **300 次/分钟**;**20 次/秒** |\n| 权限要求 | **集成中心管理**(`on-call`) |\n\n## 使用说明\n\n- 若该集成仍被其他对象引用,删除会返回 HTTP 400,`error.code` 为 `ReferenceExist`,`data.refs` 列出引用方。\n- 每次调用都会记录到账户审计日志。\n- 这些接口仅管理公共(账户级)集成,不管理协作空间专属集成。\n- 写操作还需对象权限:主体账户、管理员、创建者或所属团队成员可操作;未设置团队时,拥有集成中心管理权限的成员可操作,只读集成不可修改。", "href": "/zh/api-reference/on-call/integrations/integration-api-write-delete", "metadata": { "sidebarTitle": "删除集成" @@ -67538,7 +67665,7 @@ "On-call/集成中心" ], "x-mint": { - "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **300 次/分钟**;**20 次/秒** |\n| 权限要求 | **集成中心管理**(`on-call`) |\n\n## 使用说明\n\n- 启用会恢复该集成的拉取任务与心跳,并要求当前套餐包含该集成类型。\n- 每次调用都会记录到账户审计日志,请不要把敏感信息放在请求字段中。", + "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **300 次/分钟**;**20 次/秒** |\n| 权限要求 | **集成中心管理**(`on-call`) |\n\n## 使用说明\n\n- 启用会恢复该集成的拉取任务与心跳,并要求当前套餐包含该集成类型。\n- 每次调用都会记录到账户审计日志。\n- 这些接口仅管理公共(账户级)集成,不管理协作空间专属集成。\n- 写操作还需对象权限:主体账户、管理员、创建者或所属团队成员可操作;未设置团队时,拥有集成中心管理权限的成员可操作,只读集成不可修改。", "href": "/zh/api-reference/on-call/integrations/integration-api-write-enable", "metadata": { "sidebarTitle": "启用集成" @@ -67608,7 +67735,7 @@ "On-call/集成中心" ], "x-mint": { - "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **300 次/分钟**;**20 次/秒** |\n| 权限要求 | **集成中心管理**(`on-call`) |\n\n## 使用说明\n\n- 禁用后停止接收事件并暂停定时拉取任务,配置保留,可再次启用。\n- 每次调用都会记录到账户审计日志,请不要把敏感信息放在请求字段中。", + "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **300 次/分钟**;**20 次/秒** |\n| 权限要求 | **集成中心管理**(`on-call`) |\n\n## 使用说明\n\n- 禁用后停止接收事件并暂停定时拉取任务,配置保留,可再次启用。\n- 每次调用都会记录到账户审计日志。\n- 这些接口仅管理公共(账户级)集成,不管理协作空间专属集成。\n- 写操作还需对象权限:主体账户、管理员、创建者或所属团队成员可操作;未设置团队时,拥有集成中心管理权限的成员可操作,只读集成不可修改。", "href": "/zh/api-reference/on-call/integrations/integration-api-write-disable", "metadata": { "sidebarTitle": "禁用集成" @@ -67678,7 +67805,7 @@ "On-call/集成中心" ], "x-mint": { - "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **300 次/分钟**;**20 次/秒** |\n| 权限要求 | **集成中心管理**(`on-call`) |\n\n## 使用说明\n\n- 旧 Key 会立即失效,请先更新推送侧再轮换。\n- 新 Key 只返回一次,之后无法再次读取。\n- 每次调用都会记录到账户审计日志,请不要把敏感信息放在请求字段中。", + "content": "## 限制说明\n\n| 项目 | 说明 |\n| ---- | ---- |\n| 速率限制 | 每个账户 **300 次/分钟**;**20 次/秒** |\n| 权限要求 | **集成中心管理**(`on-call`) |\n\n## 使用说明\n\n- 旧 Key 会立即失效;轮换后请将推送端配置更新为新 Key。\n- 新 Key 也可通过 `/integration/list` 和 `/integration/info` 查询。\n- 这些接口仅管理公共(账户级)集成,不管理协作空间专属集成。\n- 写操作还需对象权限:主体账户、管理员、创建者或所属团队成员可操作;未设置团队时,拥有集成中心管理权限的成员可操作,只读集成不可修改。\n- 每次调用都会记录到账户审计日志。", "href": "/zh/api-reference/on-call/integrations/integration-api-write-rotate-key", "metadata": { "sidebarTitle": "轮换集成 Key" @@ -67707,7 +67834,7 @@ "example": { "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", "data": { - "integration_key": "9f2c7d1b45a86e30f7b1c9d4a2e65738123" + "integration_key": "0123456789abcdef0123456789abcdef100" } } } From 577bf0504904b683160ccc7c7c448883d5661669 Mon Sep 17 00:00:00 2001 From: alick-liming Date: Mon, 12 Oct 2026 11:47:38 +0800 Subject: [PATCH 2/2] fix: preserve published automation timezone in integration SDK branch --- automations_models.go | 5 +++++ automations_models_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 automations_models_test.go diff --git a/automations_models.go b/automations_models.go index fc7f260..e7cda56 100644 --- a/automations_models.go +++ b/automations_models.go @@ -14,6 +14,11 @@ type AutomationRuleUpdateRequest struct { Enabled *bool `json:"enabled,omitempty" toon:"enabled,omitempty"` // Run cadence. Supports 4 fields (`hour day month weekday`, minute defaults to 0) and 5 fields (`minute hour day month weekday`). CronExpr *string `json:"cron_expr,omitempty" toon:"cron_expr,omitempty"` + // IANA timezone `cron_expr` is evaluated in. The server must be able to load + // the name; an invalid value is rejected. Nil leaves the stored timezone + // unchanged, including a legacy empty string. An explicit empty string is + // stored as UTC, not reset to the account default. + Timezone *string `json:"timezone,omitempty" toon:"timezone,omitempty"` // Whether the schedule trigger is enabled. ScheduleTriggerEnabled *bool `json:"schedule_trigger_enabled,omitempty" toon:"schedule_trigger_enabled,omitempty"` // New task prompt. diff --git a/automations_models_test.go b/automations_models_test.go new file mode 100644 index 0000000..9730db2 --- /dev/null +++ b/automations_models_test.go @@ -0,0 +1,33 @@ +package flashduty + +import ( + "encoding/json" + "strings" + "testing" +) + +func TestAutomationRuleUpdateRequestTimezone(t *testing.T) { + omitted, err := json.Marshal(&AutomationRuleUpdateRequest{RuleID: "arule_1"}) + if err != nil { + t.Fatal(err) + } + if strings.Contains(string(omitted), "timezone") { + t.Fatalf("nil timezone must be omitted, got %s", omitted) + } + + empty, err := json.Marshal(&AutomationRuleUpdateRequest{RuleID: "arule_1", Timezone: String("")}) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(empty), `"timezone":""`) { + t.Fatalf("empty timezone must be sent, got %s", empty) + } + + named, err := json.Marshal(&AutomationRuleUpdateRequest{RuleID: "arule_1", Timezone: String("Asia/Shanghai")}) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(named), `"timezone":"Asia/Shanghai"`) { + t.Fatalf("named timezone must be sent, got %s", named) + } +}