diff --git a/backend/plugins/pagerduty/api/blueprint_v200.go b/backend/plugins/pagerduty/api/blueprint_v200.go index 2e088b4fd5b..bfefcd8553b 100644 --- a/backend/plugins/pagerduty/api/blueprint_v200.go +++ b/backend/plugins/pagerduty/api/blueprint_v200.go @@ -64,14 +64,15 @@ func makePipelinePlanV200( } scope, scopeConfig := scopeDetail.Scope, scopeDetail.ScopeConfig - // construct task options for circleci + // construct task options for pagerduty task, err := api.MakePipelinePlanTask( "pagerduty", subtaskMetas, scopeConfig.Entities, tasks.PagerDutyOptions{ - ConnectionId: connection.ID, - ServiceId: scope.Id, + ConnectionId: connection.ID, + ServiceId: scope.Id, + ScopeConfigId: scopeConfig.ID, }, ) if err != nil { diff --git a/backend/plugins/pagerduty/api/scope_config_api.go b/backend/plugins/pagerduty/api/scope_config_api.go new file mode 100644 index 00000000000..9ef8bd72494 --- /dev/null +++ b/backend/plugins/pagerduty/api/scope_config_api.go @@ -0,0 +1,111 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package api + +import ( + "github.com/apache/incubator-devlake/core/errors" + "github.com/apache/incubator-devlake/core/plugin" +) + +// CreateScopeConfig create scope config for PagerDuty +// @Summary create scope config for PagerDuty +// @Description create scope config for PagerDuty +// @Tags plugins/pagerduty +// @Accept application/json +// @Param connectionId path int true "connectionId" +// @Param scopeConfig body models.PagerdutyScopeConfig true "scope config" +// @Success 200 {object} models.PagerdutyScopeConfig +// @Failure 400 {object} shared.ApiBody "Bad Request" +// @Failure 500 {object} shared.ApiBody "Internal Error" +// @Router /plugins/pagerduty/connections/{connectionId}/scope-configs [POST] +func CreateScopeConfig(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { + return dsHelper.ScopeConfigApi.Post(input) +} + +// PatchScopeConfig update scope config for PagerDuty +// @Summary update scope config for PagerDuty +// @Description update scope config for PagerDuty +// @Tags plugins/pagerduty +// @Accept application/json +// @Param id path int true "id" +// @Param connectionId path int true "connectionId" +// @Param scopeConfig body models.PagerdutyScopeConfig true "scope config" +// @Success 200 {object} models.PagerdutyScopeConfig +// @Failure 400 {object} shared.ApiBody "Bad Request" +// @Failure 500 {object} shared.ApiBody "Internal Error" +// @Router /plugins/pagerduty/connections/{connectionId}/scope-configs/{id} [PATCH] +func PatchScopeConfig(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { + return dsHelper.ScopeConfigApi.Patch(input) +} + +// GetScopeConfig return one scope config +// @Summary return one scope config +// @Description return one scope config +// @Tags plugins/pagerduty +// @Param id path int true "id" +// @Param connectionId path int true "connectionId" +// @Success 200 {object} models.PagerdutyScopeConfig +// @Failure 400 {object} shared.ApiBody "Bad Request" +// @Failure 500 {object} shared.ApiBody "Internal Error" +// @Router /plugins/pagerduty/connections/{connectionId}/scope-configs/{id} [GET] +func GetScopeConfig(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { + return dsHelper.ScopeConfigApi.GetDetail(input) +} + +// GetScopeConfigList return all scope configs +// @Summary return all scope configs +// @Description return all scope configs +// @Tags plugins/pagerduty +// @Param connectionId path int true "connectionId" +// @Param pageSize query int false "page size, default 50" +// @Param page query int false "page size, default 1" +// @Success 200 {object} []models.PagerdutyScopeConfig +// @Failure 400 {object} shared.ApiBody "Bad Request" +// @Failure 500 {object} shared.ApiBody "Internal Error" +// @Router /plugins/pagerduty/connections/{connectionId}/scope-configs [GET] +func GetScopeConfigList(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { + return dsHelper.ScopeConfigApi.GetAll(input) +} + +// GetServicesByScopeConfig return services details related by scope config +// @Summary return all related services +// @Description return all related services +// @Tags plugins/pagerduty +// @Param id path int true "id" +// @Param scopeConfigId path int true "scopeConfigId" +// @Success 200 {object} models.ProjectScopeOutput +// @Failure 400 {object} shared.ApiBody "Bad Request" +// @Failure 500 {object} shared.ApiBody "Internal Error" +// @Router /plugins/pagerduty/scope-config/{scopeConfigId}/projects [GET] +func GetServicesByScopeConfig(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { + return dsHelper.ScopeConfigApi.GetProjectsByScopeConfig(input) +} + +// DeleteScopeConfig delete a scope config +// @Summary delete a scope config +// @Description delete a scope config +// @Tags plugins/pagerduty +// @Param id path int true "id" +// @Param connectionId path int true "connectionId" +// @Success 200 +// @Failure 400 {object} shared.ApiBody "Bad Request" +// @Failure 500 {object} shared.ApiBody "Internal Error" +// @Router /plugins/pagerduty/connections/{connectionId}/scope-configs/{id} [DELETE] +func DeleteScopeConfig(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) { + return dsHelper.ScopeConfigApi.Delete(input) +} diff --git a/backend/plugins/pagerduty/impl/impl.go b/backend/plugins/pagerduty/impl/impl.go index 417156b72ff..4ae53cb2bf0 100644 --- a/backend/plugins/pagerduty/impl/impl.go +++ b/backend/plugins/pagerduty/impl/impl.go @@ -70,7 +70,7 @@ func (p PagerDuty) Scope() plugin.ToolLayerScope { } func (p PagerDuty) ScopeConfig() dal.Tabler { - return nil + return &models.PagerdutyScopeConfig{} } func (p PagerDuty) SubTaskMetas() []plugin.SubTaskMeta { @@ -109,6 +109,21 @@ func (p PagerDuty) PrepareTaskData(taskCtx plugin.TaskContext, options map[strin return nil, errors.Default.Wrap(err, "unable to get Pagerduty connection by the given connection ID") } + // Load ScopeConfig from database if only ScopeConfigId is provided + if op.ScopeConfig == nil && op.ScopeConfigId != 0 { + var scopeConfig models.PagerdutyScopeConfig + db := taskCtx.GetDal() + err = db.First(&scopeConfig, dal.Where("id = ?", op.ScopeConfigId)) + if err != nil && !db.IsErrorNotFound(err) { + return nil, errors.BadInput.Wrap(err, "fail to get scopeConfig") + } + op.ScopeConfig = &scopeConfig + } + // Initialize empty ScopeConfig if none provided + if op.ScopeConfig == nil { + op.ScopeConfig = new(models.PagerdutyScopeConfig) + } + client, err := helper.NewApiClientFromConnection(taskCtx.GetContext(), taskCtx, connection) if err != nil { @@ -168,6 +183,18 @@ func (p PagerDuty) ApiResources() map[string]map[string]plugin.ApiResourceHandle "connections/:connectionId/scopes/:scopeId/latest-sync-state": { "GET": api.GetScopeLatestSyncState, }, + "connections/:connectionId/scope-configs": { + "POST": api.CreateScopeConfig, + "GET": api.GetScopeConfigList, + }, + "connections/:connectionId/scope-configs/:scopeConfigId": { + "PATCH": api.PatchScopeConfig, + "GET": api.GetScopeConfig, + "DELETE": api.DeleteScopeConfig, + }, + "connections/:connectionId/scope-configs/:scopeConfigId/projects": { + "GET": api.GetServicesByScopeConfig, + }, } } diff --git a/backend/plugins/pagerduty/models/migrationscripts/20251003_add_filter_fields_to_scope_configs.go b/backend/plugins/pagerduty/models/migrationscripts/20251003_add_filter_fields_to_scope_configs.go new file mode 100644 index 00000000000..9b6aee95238 --- /dev/null +++ b/backend/plugins/pagerduty/models/migrationscripts/20251003_add_filter_fields_to_scope_configs.go @@ -0,0 +1,49 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package migrationscripts + +import ( + "github.com/apache/incubator-devlake/core/context" + "github.com/apache/incubator-devlake/core/errors" + "github.com/apache/incubator-devlake/core/plugin" +) + +var _ plugin.MigrationScript = (*addFilterFieldsToPagerDutyScopeConfig20251003)(nil) + +type PagerDutyScopeConfig20251003 struct { + PriorityFilter []string `mapstructure:"priorityFilter" json:"priorityFilter" gorm:"type:text;serializer:json"` + UrgencyFilter []string `mapstructure:"urgencyFilter" json:"urgencyFilter" gorm:"type:text;serializer:json"` +} + +func (o PagerDutyScopeConfig20251003) TableName() string { + return "_tool_pagerduty_scope_configs" +} + +type addFilterFieldsToPagerDutyScopeConfig20251003 struct{} + +func (script *addFilterFieldsToPagerDutyScopeConfig20251003) Up(basicRes context.BasicRes) errors.Error { + return basicRes.GetDal().AutoMigrate(&PagerDutyScopeConfig20251003{}) +} + +func (*addFilterFieldsToPagerDutyScopeConfig20251003) Version() uint64 { + return 20251003000000 +} + +func (script *addFilterFieldsToPagerDutyScopeConfig20251003) Name() string { + return "add priority_filter and urgency_filter fields to table _tool_pagerduty_scope_configs" +} diff --git a/backend/plugins/pagerduty/models/migrationscripts/20251003_add_scope_config_id_to_services.go b/backend/plugins/pagerduty/models/migrationscripts/20251003_add_scope_config_id_to_services.go new file mode 100644 index 00000000000..cef63fca2c8 --- /dev/null +++ b/backend/plugins/pagerduty/models/migrationscripts/20251003_add_scope_config_id_to_services.go @@ -0,0 +1,48 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package migrationscripts + +import ( + "github.com/apache/incubator-devlake/core/context" + "github.com/apache/incubator-devlake/core/errors" + "github.com/apache/incubator-devlake/core/plugin" +) + +var _ plugin.MigrationScript = (*addScopeConfigIdToServices20251003)(nil) + +type service20251003 struct { + ScopeConfigId uint64 `gorm:"column:scope_config_id"` +} + +func (service20251003) TableName() string { + return "_tool_pagerduty_services" +} + +type addScopeConfigIdToServices20251003 struct{} + +func (script *addScopeConfigIdToServices20251003) Up(basicRes context.BasicRes) errors.Error { + return basicRes.GetDal().AutoMigrate(&service20251003{}) +} + +func (*addScopeConfigIdToServices20251003) Version() uint64 { + return 20251003000001 +} + +func (script *addScopeConfigIdToServices20251003) Name() string { + return "add scope_config_id to _tool_pagerduty_services" +} diff --git a/backend/plugins/pagerduty/models/migrationscripts/register.go b/backend/plugins/pagerduty/models/migrationscripts/register.go index a186f8cb04b..73f458e5c16 100644 --- a/backend/plugins/pagerduty/models/migrationscripts/register.go +++ b/backend/plugins/pagerduty/models/migrationscripts/register.go @@ -34,5 +34,7 @@ func All() []plugin.MigrationScript { new(addIncidentPriority), new(addPagerDutyScopeConfig20231214), new(addPagerDutyScopeConfig20240614), + new(addFilterFieldsToPagerDutyScopeConfig20251003), + new(addScopeConfigIdToServices20251003), } } diff --git a/backend/plugins/pagerduty/models/scope_config.go b/backend/plugins/pagerduty/models/scope_config.go index 6d4a89df86c..926052c2247 100644 --- a/backend/plugins/pagerduty/models/scope_config.go +++ b/backend/plugins/pagerduty/models/scope_config.go @@ -23,6 +23,8 @@ import ( type PagerdutyScopeConfig struct { common.ScopeConfig `mapstructure:",squash" json:",inline" gorm:"embedded"` + PriorityFilter []string `mapstructure:"priorityFilter" json:"priorityFilter" gorm:"type:text;serializer:json"` + UrgencyFilter []string `mapstructure:"urgencyFilter" json:"urgencyFilter" gorm:"type:text;serializer:json"` } func (p PagerdutyScopeConfig) TableName() string { diff --git a/backend/plugins/pagerduty/tasks/incidents_collector.go b/backend/plugins/pagerduty/tasks/incidents_collector.go index 91068bf5bea..09c01507382 100644 --- a/backend/plugins/pagerduty/tasks/incidents_collector.go +++ b/backend/plugins/pagerduty/tasks/incidents_collector.go @@ -99,6 +99,21 @@ func CollectIncidents(taskCtx plugin.SubTaskContext) errors.Error { query.Set("limit", fmt.Sprintf("%d", reqData.Pager.Size)) query.Set("offset", fmt.Sprintf("%d", reqData.Pager.Skip)) query.Set("total", "true") + + // Apply Priority filters if configured + if data.Options.ScopeConfig != nil && len(data.Options.ScopeConfig.PriorityFilter) > 0 { + for _, priority := range data.Options.ScopeConfig.PriorityFilter { + query.Add("priorities[]", priority) + } + } + + // Apply Urgency filters if configured + if data.Options.ScopeConfig != nil && len(data.Options.ScopeConfig.UrgencyFilter) > 0 { + for _, urgency := range data.Options.ScopeConfig.UrgencyFilter { + query.Add("urgencies[]", urgency) + } + } + return query, nil }, ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) { diff --git a/config-ui/src/api/scope-config/index.ts b/config-ui/src/api/scope-config/index.ts index 41547d9c058..dfa0ee988b8 100644 --- a/config-ui/src/api/scope-config/index.ts +++ b/config-ui/src/api/scope-config/index.ts @@ -38,5 +38,5 @@ export const update = (plugin: string, connectionId: ID, id: ID, data: any) => data, }); -export const check = (plugin: string, id: ID): Promise => - request(`/plugins/${plugin}/scope-config/${id}/projects`); +export const check = (plugin: string, connectionId: ID, id: ID): Promise => + request(`/plugins/${plugin}/connections/${connectionId}/scope-configs/${id}/projects`); diff --git a/config-ui/src/plugins/components/scope-config-form/index.tsx b/config-ui/src/plugins/components/scope-config-form/index.tsx index 5c27d84d454..177bab5b4fa 100644 --- a/config-ui/src/plugins/components/scope-config-form/index.tsx +++ b/config-ui/src/plugins/components/scope-config-form/index.tsx @@ -34,6 +34,7 @@ import { AzureTransformation } from '@/plugins/register/azure'; import { TapdTransformation } from '@/plugins/register/tapd'; import { BambooTransformation } from '@/plugins/register/bamboo'; import { CircleCITransformation } from '@/plugins/register/circleci'; +import { PagerDutyTransformation } from '@/plugins/register/pagerduty'; import { DOC_URL } from '@/release'; import { operator } from '@/utils'; @@ -285,6 +286,13 @@ export const ScopeConfigForm = ({ setTransformation={setTransformation} /> )} + + {plugin === 'pagerduty' && ( + + )} diff --git a/config-ui/src/plugins/components/scope-config/index.tsx b/config-ui/src/plugins/components/scope-config/index.tsx index 50756a4b33c..e5f13e0bb68 100644 --- a/config-ui/src/plugins/components/scope-config/index.tsx +++ b/config-ui/src/plugins/components/scope-config/index.tsx @@ -71,7 +71,7 @@ export const ScopeConfig = ({ const handleHideDialog = () => setType(undefined); const handleCheckScopeConfig = async (id: ID) => { - const [success, res] = await operator(() => API.scopeConfig.check(plugin, id), { hideToast: true }); + const [success, res] = await operator(() => API.scopeConfig.check(plugin, connectionId, id), { hideToast: true }); if (success) { const projects = (res.projects ?? []).map((it: any) => ({ @@ -189,7 +189,7 @@ export const ScopeConfig = ({ const handleUpdate = async (trId: ID) => { handleHideDialog(); - const [success, res] = await operator(() => API.scopeConfig.check(plugin, trId), { hideToast: true }); + const [success, res] = await operator(() => API.scopeConfig.check(plugin, connectionId, trId), { hideToast: true }); if (success) { handleShowProjectsModal(res.projects ?? []); diff --git a/config-ui/src/plugins/register/pagerduty/config.tsx b/config-ui/src/plugins/register/pagerduty/config.tsx index 5d7ef4ed79d..f9c6b8618b4 100644 --- a/config-ui/src/plugins/register/pagerduty/config.tsx +++ b/config-ui/src/plugins/register/pagerduty/config.tsx @@ -55,4 +55,11 @@ export const PagerDutyConfig: IPluginConfig = { dataScope: { title: 'Services', }, + scopeConfig: { + entities: ['TICKET'], + transformation: { + priorityFilter: [], + urgencyFilter: [], + }, + }, }; diff --git a/config-ui/src/plugins/register/pagerduty/index.ts b/config-ui/src/plugins/register/pagerduty/index.ts index de415db39ab..5f16858cbe4 100644 --- a/config-ui/src/plugins/register/pagerduty/index.ts +++ b/config-ui/src/plugins/register/pagerduty/index.ts @@ -17,3 +17,4 @@ */ export * from './config'; +export * from './transformation'; diff --git a/config-ui/src/plugins/register/pagerduty/transformation.tsx b/config-ui/src/plugins/register/pagerduty/transformation.tsx new file mode 100644 index 00000000000..ed38a9feab9 --- /dev/null +++ b/config-ui/src/plugins/register/pagerduty/transformation.tsx @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Form, Select } from 'antd'; +import { HelpTooltip } from '@/components'; + +interface Props { + transformation: any; + setTransformation: React.Dispatch>; +} + +export const PagerDutyTransformation = ({ transformation, setTransformation }: Props) => { + const priorityOptions = [ + { label: 'P1', value: 'P1' }, + { label: 'P2', value: 'P2' }, + { label: 'P3', value: 'P3' }, + { label: 'P4', value: 'P4' }, + { label: 'P5', value: 'P5' }, + ]; + + const urgencyOptions = [ + { label: 'High', value: 'high' }, + { label: 'Low', value: 'low' }, + ]; + + return ( + <> + + Priority Filter + + + } + > + + setTransformation({ + ...transformation, + urgencyFilter: value, + }) + } + /> + + + ); +};