You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by li...@apache.org on 2022/11/22 15:42:02 UTC
[incubator-devlake] branch main updated: [feat-3498][backend] Pagerduty plugin implementation (#3641)
This is an automated email from the ASF dual-hosted git repository.
likyh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
The following commit(s) were added to refs/heads/main by this push:
new eef06e96e [feat-3498][backend] Pagerduty plugin implementation (#3641)
eef06e96e is described below
commit eef06e96e95ad9e95fd9bc4dde27cc55123e0740
Author: Keon Amini <ke...@merico.dev>
AuthorDate: Tue Nov 22 09:41:56 2022 -0600
[feat-3498][backend] Pagerduty plugin implementation (#3641)
* feat: Pagerduty plugin implementation
* refactor: change endpoint for token validation
* fix: added domain types to subtasks
* fix: removed deploymentId assignment from converter
* refactor: pluralized database table names
---
.env.example | 4 +-
.licenserc.yaml | 1 +
Dockerfile | 8 +
Makefile | 5 +
README.md | 24 +-
config/config.go | 1 +
config/tap/pagerduty.json | 1884 ++++++++++++++++++++
helpers/pluginhelper/tap/singer_models.go | 2 +-
helpers/pluginhelper/tap/singer_tap_client.go | 47 -
helpers/pluginhelper/tap/singer_tap_impl.go | 47 +-
helpers/pluginhelper/tap/tap_collector.go | 6 +-
plugins/pagerduty/api/blueprint.go | 64 +
plugins/pagerduty/api/connection.go | 150 ++
plugins/pagerduty/api/init.go | 39 +
plugins/pagerduty/e2e/incident_test.go | 86 +
.../e2e/raw_tables/_raw_pagerduty_incidents.csv | 4 +
.../_tool_pagerduty_assignments.csv | 4 +
.../snapshot_tables/_tool_pagerduty_incidents.csv | 4 +
.../snapshot_tables/_tool_pagerduty_services.csv | 2 +
.../e2e/snapshot_tables/_tool_pagerduty_users.csv | 3 +
plugins/pagerduty/e2e/snapshot_tables/issues.csv | 4 +
plugins/pagerduty/impl/impl.go | 148 ++
plugins/pagerduty/models/assignment.go | 35 +
plugins/pagerduty/models/config.go | 34 +
plugins/pagerduty/models/connection.go | 52 +
plugins/pagerduty/models/consts.go | 25 +
plugins/pagerduty/models/generated/incidents.go | 491 +++++
.../pagerduty/models/generated/notifications.go | 55 +
plugins/pagerduty/models/generated/services.go | 205 +++
plugins/pagerduty/models/incident.go | 51 +
.../migrationscripts/20221115_add_init_tables.go | 46 +
.../models/migrationscripts/archived/assignment.go | 35 +
.../models/migrationscripts/archived/connection.go | 30 +
.../models/migrationscripts/archived/incident.go | 40 +
.../models/migrationscripts/archived/service.go | 34 +
.../models/migrationscripts/archived/user.go | 32 +
.../pagerduty/models/migrationscripts/register.go | 29 +
plugins/pagerduty/models/service.go | 32 +
plugins/pagerduty/models/user.go | 32 +
plugins/pagerduty/pager_duty.go | 43 +
plugins/pagerduty/tasks/incidents_collector.go | 62 +
plugins/pagerduty/tasks/incidents_converter.go | 143 ++
plugins/pagerduty/tasks/incidents_extractor.go | 107 ++
plugins/pagerduty/tasks/task_data.go | 52 +
requirements.txt | 3 +
scripts/singer-model-generator.sh | 107 +-
utils/ipc.go | 18 +
47 files changed, 4198 insertions(+), 132 deletions(-)
diff --git a/.env.example b/.env.example
index 19b43e579..4d71e61ed 100644
--- a/.env.example
+++ b/.env.example
@@ -31,8 +31,8 @@ LOGGING_DIR=
ENABLE_STACKTRACE=false
FORCE_MIGRATION=false
-# Lake SINGER API
-SINGER_PROPERTIES_DIR=
+# Lake TAP API
+TAP_PROPERTIES_DIR=
##########################
# Sensitive information encryption key
diff --git a/.licenserc.yaml b/.licenserc.yaml
index 3f79f5dc0..bc33837f8 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -44,6 +44,7 @@ header:
- 'DISCLAIMER'
- 'go.mod'
- 'go.sum'
+ - 'requirements.txt'
- '**/.babelrc'
- '**/empty'
- '**/*.conf'
diff --git a/Dockerfile b/Dockerfile
index e3aeb0dd5..1ee0ecd35 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -45,6 +45,14 @@ WORKDIR /app
COPY --from=builder /app/bin /app/bin
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
+COPY --from=builder /app/requirements.txt /app/requirements.txt
+COPY --from=builder /app/config/tap /app/config/tap
+
+# Setup Python
+RUN python -m venv /app/.venv
+RUN echo "source /app/.venv/bin/activate" >> ~/.profile
+RUN source ~/.profile
+RUN pip install --upgrade pip -r requirements.txt
ENV PATH="/app/bin:${PATH}"
diff --git a/Makefile b/Makefile
index 6bd35581b..e818a7349 100644
--- a/Makefile
+++ b/Makefile
@@ -25,6 +25,8 @@ VERSION = $(TAG)@$(SHA)
dep:
go install github.com/vektra/mockery/v2@latest
go install github.com/swaggo/swag/cmd/swag@v1.8.4
+ go install github.com/atombender/go-jsonschema/cmd/gojsonschema@latest
+ pip install -r requirements.txt
swag:
swag init --parseDependency --parseInternal -o ./api/docs -g ./api/api.go -g plugins/*/api/*.go
@@ -57,6 +59,9 @@ build-grafana-image:
build-images: build-server-image build-config-ui-image build-grafana-image
+tap-models:
+ chmod +x ./scripts/singer-model-generator.sh
+ @sh scripts/singer-model-generator.sh config/singer/pagerduty.json plugins/pagerduty --all
push-server-image: build-server-image
docker push $(IMAGE_REPO)/devlake:$(TAG)
diff --git a/README.md b/README.md
index d62b338f9..d40edab0f 100644
--- a/README.md
+++ b/README.md
@@ -33,18 +33,18 @@ Apache DevLake is designed for developer teams looking to make better sense of t
## 💪 Supported Data Sources
-| Data Source | Domain(s) | Supported Versions | Config UI Availability | Triggered Plugins | Collection Mode |
-|-------------------------------|------------------------------------------------------------|--------------------------------------|------------------------|---------------------------- | --------------------- |
-| GitHub (include GitHub Action)| Source Code Management, Code Review, Issue Tracking, CI/CD | Cloud |Available |`github`, `gitextractor` | Full Refresh, Incremental Sync(for `issues`, `PRs`) |
-| GitLab (include GitLabCI) | Source Code Management, Code Review, Issue Tracking, CI/CD | Cloud, Community Edition 13.x+ |Available |`gitlab`, `gitextractor` | Full Refresh, Incremental Sync(for `issues`)|
-| Gitee | Source Code Management, Code Review, Issue Tracking | Cloud |Not Available |`gitee`, `gitextractor` | Incremental Sync |
-| BitBucket | Source Code Management, Code Review | Cloud |Not Available |`bitbucket`, `gitextractor` | Full Refresh |
-| Jira | Issue Tracking | Cloud, Server 8.x+, Data Center 8.x+ |Available |`jira` | Full Refresh, Incremental Sync(for `issues`, `changelogs`, `worklogs`) |
-| TAPD | Issue Tracking | Cloud |Not Available |`tapd` | Full Refresh, Incremental Sync(for `stories`, `bugs`, `tasks`) |
-| Jenkins | CI/CD | 2.263.x+ |Available |`jenkins` | Full Refresh |
-| Feishu | Calendar | Cloud |Not Available |`feishu` | Full Refresh |
-| AE | Source Code Management | |Not Available | `ae` | Full Refresh |
-
+| Data Source | Domain(s) | Supported Versions | Config UI Availability | Triggered Plugins | Collection Mode |
+|-------------------------------|------------------------------------------------------------|-------------------------------------------------------------|------------------------|---------------------------- | --------------------- |
+| GitHub (include GitHub Action)| Source Code Management, Code Review, Issue Tracking, CI/CD | Cloud |Available |`github`, `gitextractor` | Full Refresh, Incremental Sync(for `issues`, `PRs`) |
+| GitLab (include GitLabCI) | Source Code Management, Code Review, Issue Tracking, CI/CD | Cloud, Community Edition 13.x+ |Available |`gitlab`, `gitextractor` | Full Refresh, Incremental Sync(for `issues`)|
+| Gitee | Source Code Management, Code Review, Issue Tracking | Cloud |Not Available |`gitee`, `gitextractor` | Incremental Sync |
+| BitBucket | Source Code Management, Code Review | Cloud |Not Available |`bitbucket`, `gitextractor` | Full Refresh |
+| Jira | Issue Tracking | Cloud, Server 8.x+, Data Center 8.x+ |Available |`jira` | Full Refresh, Incremental Sync(for `issues`, `changelogs`, `worklogs`) |
+| TAPD | Issue Tracking | Cloud |Not Available |`tapd` | Full Refresh, Incremental Sync(for `stories`, `bugs`, `tasks`) |
+| Jenkins | CI/CD | 2.263.x+ |Available |`jenkins` | Full Refresh |
+| Feishu | Calendar | Cloud |Not Available |`feishu` | Full Refresh |
+| AE | Source Code Management | |Not Available | `ae` | Full Refresh |
+| Pagerduty | Issue Tracking | [Singer-tap](https://github.com/singer-io/tap-pagerduty) |Not Available | `pagerduty` | Full Refresh |
## 🚀 Getting Started
diff --git a/config/config.go b/config/config.go
index d83d28396..282e86e6c 100644
--- a/config/config.go
+++ b/config/config.go
@@ -70,6 +70,7 @@ func setDefaultValue(v *viper.Viper) {
v.SetDefault("PORT", ":8080")
v.SetDefault("PLUGIN_DIR", "bin/plugins")
v.SetDefault("TEMPORAL_TASK_QUEUE", "DEVLAKE_TASK_QUEUE")
+ v.SetDefault("TAP_PROPERTIES_DIR", "config/tap")
}
// replaceNewEnvItemInOldContent replace old config to new config in env file content
diff --git a/config/tap/pagerduty.json b/config/tap/pagerduty.json
new file mode 100644
index 000000000..e76c54ed1
--- /dev/null
+++ b/config/tap/pagerduty.json
@@ -0,0 +1,1884 @@
+{
+ "streams": [
+ {
+ "tap_stream_id": "incidents",
+ "replication_key": "last_status_change_at",
+ "replication_method": "FULL_TABLE",
+ "key_properties": "id",
+ "schema": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "incident_number": {
+ "type": [
+ "null",
+ "integer"
+ ]
+ },
+ "created_at": {
+ "format": "date-time",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "status": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "title": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "incident_key": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "service": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "priority": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "assignments": {
+ "items": {
+ "properties": {
+ "at": {
+ "format": "date-time",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "assignee": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "type": [
+ "null",
+ "array"
+ ]
+ },
+ "acknowledgements": {
+ "items": {
+ "properties": {
+ "at": {
+ "format": "date-time",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "acknowledger": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "type": [
+ "null",
+ "array"
+ ]
+ },
+ "last_status_change_at": {
+ "format": "date-time",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "last_status_change_by": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "first_trigger_log_entry": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "escalation_policy": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "teams": {
+ "items": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "type": [
+ "null",
+ "array"
+ ]
+ },
+ "urgency": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "conference_bridge": {
+ "properties": {
+ "conference_number": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "conference_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "log_entries": {
+ "items": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "created_at": {
+ "format": "date-time",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "agent": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "channel": {
+ "properties": {
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "incident": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "teams": {
+ "items": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "type": [
+ "null",
+ "array"
+ ]
+ },
+ "event_details": {
+ "properties": {
+ "description": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "type": [
+ "null",
+ "array"
+ ]
+ },
+ "alerts": {
+ "items": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "created_at": {
+ "format": "date-time",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "status": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "alert_key": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "service": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "body": {
+ "properties": {
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "contexts": {
+ "items": {
+ "properties": {
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "href": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "src": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "text": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "type": [
+ "null",
+ "array"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "incident": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "suppressed": {
+ "type": [
+ "null",
+ "boolean"
+ ]
+ },
+ "severity": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "integration": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "name": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "service": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "type": [
+ "null",
+ "array"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ],
+ "additionalProperties": false
+ },
+ "stream": "incidents",
+ "metadata": [
+ {
+ "breadcrumb": [],
+ "metadata": {
+ "table-key-properties": "id",
+ "forced-replication-method": "FULL_TABLE",
+ "valid-replication-keys": [
+ "last_status_change_at"
+ ],
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "id"
+ ],
+ "metadata": {
+ "inclusion": "automatic"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "type"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "summary"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "self"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "html_url"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "incident_number"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "created_at"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "status"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "title"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "incident_key"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "service"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "priority"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "assignments"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "acknowledgements"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "last_status_change_at"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "last_status_change_by"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "first_trigger_log_entry"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "escalation_policy"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "teams"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "urgency"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "conference_bridge"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "log_entries"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "alerts"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ }
+ ]
+ },
+ {
+ "tap_stream_id": "services",
+ "replication_method": "FULL_TABLE",
+ "key_properties": "id",
+ "schema": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "name": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "description": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "auto_resolve_timeout": {
+ "type": [
+ "null",
+ "integer"
+ ]
+ },
+ "acknowledgement_timeout": {
+ "type": [
+ "null",
+ "integer"
+ ]
+ },
+ "created_at": {
+ "format": "date-time",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "status": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "last_incident_timestamp": {
+ "format": "date-time",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "alert_creation": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "alert_grouping": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "alert_grouping_timeout": {
+ "type": [
+ "null",
+ "integer"
+ ]
+ },
+ "integrations": {
+ "items": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "type": [
+ "null",
+ "array"
+ ]
+ },
+ "escalation_policy": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "teams": {
+ "items": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "type": [
+ "null",
+ "array"
+ ]
+ },
+ "incident_urgency_rule": {
+ "properties": {
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "during_support_hours": {
+ "properties": {
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "urgency": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "outside_support_hours": {
+ "properties": {
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "urgency": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "support_hours": {
+ "properties": {
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "time_zone": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "start_time": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "end_time": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "days_of_week": {
+ "items": {
+ "type": [
+ "null",
+ "integer"
+ ]
+ },
+ "type": [
+ "null",
+ "array"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "scheduled_actions": {
+ "items": {
+ "properties": {
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "at": {
+ "properties": {
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "name": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "to_urgency": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ },
+ "type": [
+ "null",
+ "array"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ],
+ "additionalProperties": false
+ },
+ "stream": "services",
+ "metadata": [
+ {
+ "breadcrumb": [],
+ "metadata": {
+ "table-key-properties": "id",
+ "forced-replication-method": "FULL_TABLE",
+ "valid-replication-keys": [],
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "id"
+ ],
+ "metadata": {
+ "inclusion": "automatic"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "type"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "summary"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "self"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "html_url"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "name"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "description"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "auto_resolve_timeout"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "acknowledgement_timeout"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "created_at"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "status"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "last_incident_timestamp"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "alert_creation"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "alert_grouping"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "alert_grouping_timeout"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "integrations"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "escalation_policy"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "teams"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "incident_urgency_rule"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "support_hours"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "scheduled_actions"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ }
+ ]
+ },
+ {
+ "tap_stream_id": "notifications",
+ "replication_key": "started_at",
+ "replication_method": "INCREMENTAL",
+ "key_properties": "id",
+ "schema": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "started_at": {
+ "format": "date-time",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "address": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "user": {
+ "properties": {
+ "id": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "type": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "summary": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "self": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "html_url": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ]
+ }
+ },
+ "type": [
+ "null",
+ "object"
+ ],
+ "additionalProperties": false
+ },
+ "stream": "notifications",
+ "metadata": [
+ {
+ "breadcrumb": [],
+ "metadata": {
+ "table-key-properties": "id",
+ "forced-replication-method": "INCREMENTAL",
+ "valid-replication-keys": [
+ "started_at"
+ ],
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "id"
+ ],
+ "metadata": {
+ "inclusion": "automatic"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "type"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "started_at"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "address"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ },
+ {
+ "breadcrumb": [
+ "properties",
+ "user"
+ ],
+ "metadata": {
+ "inclusion": "available"
+ }
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/helpers/pluginhelper/tap/singer_models.go b/helpers/pluginhelper/tap/singer_models.go
index f6eebdd2c..ad8a0f565 100644
--- a/helpers/pluginhelper/tap/singer_models.go
+++ b/helpers/pluginhelper/tap/singer_models.go
@@ -33,7 +33,7 @@ type (
}
// SingerTapConfig the set of variables needed to initialize a SingerTap
SingerTapConfig struct {
- Cmd string
+ TapExecutable string
StreamPropertiesFile string
IsLegacy bool
}
diff --git a/helpers/pluginhelper/tap/singer_tap_client.go b/helpers/pluginhelper/tap/singer_tap_client.go
deleted file mode 100644
index 452f5ebcb..000000000
--- a/helpers/pluginhelper/tap/singer_tap_client.go
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
-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 tap
-
-import (
- "github.com/apache/incubator-devlake/config"
- "github.com/apache/incubator-devlake/errors"
-)
-
-// SingerTapArgs the args needed to instantiate tap.Tap for singer-taps
-type SingerTapArgs struct {
- // Name of the env variable that expands to the tap binary path
- TapClass string
- // The name of the properties/catalog JSON file of the tap
- StreamPropertiesFile string
- // IsLegacy - set to true if this is an old tap that uses the "--properties" flag
- IsLegacy bool
-}
-
-// NewSingerTapClient returns an instance of tap.Tap for singer-taps
-func NewSingerTapClient(args *SingerTapArgs) (*SingerTap, errors.Error) {
- env := config.GetConfig()
- cmd := env.GetString(args.TapClass)
- if cmd == "" {
- return nil, errors.Default.New("singer tap command not provided")
- }
- return NewSingerTap(&SingerTapConfig{
- Cmd: cmd,
- StreamPropertiesFile: args.StreamPropertiesFile,
- IsLegacy: args.IsLegacy,
- })
-}
diff --git a/helpers/pluginhelper/tap/singer_tap_impl.go b/helpers/pluginhelper/tap/singer_tap_impl.go
index 4786443a0..3a2c681ba 100644
--- a/helpers/pluginhelper/tap/singer_tap_impl.go
+++ b/helpers/pluginhelper/tap/singer_tap_impl.go
@@ -25,11 +25,10 @@ import (
"github.com/apache/incubator-devlake/utils"
"github.com/mitchellh/hashstructure"
"os"
- "os/exec"
"path/filepath"
)
-const singerPropertiesDir = "SINGER_PROPERTIES_DIR"
+const singerPropertiesDir = "TAP_PROPERTIES_DIR"
type (
// SingerTap the Singer implementation of Tap
@@ -58,12 +57,14 @@ func NewSingerTap(cfg *SingerTapConfig) (*SingerTap, errors.Error) {
if err != nil {
return nil, err
}
- tapName := filepath.Base(cfg.Cmd)
+ tapName := filepath.Base(cfg.TapExecutable)
return &SingerTap{
- cmd: cfg.Cmd,
+ cmd: cfg.TapExecutable,
name: tapName,
tempLocation: tempDir,
propertiesFile: propsFile,
+ stateFile: new(fileData[[]byte]),
+ configFile: new(fileData[[]byte]),
SingerTapConfig: cfg,
}, nil
}
@@ -127,15 +128,14 @@ func (t *SingerTap) GetName() string {
// Run implements Tap.Run
func (t *SingerTap) Run() (<-chan *utils.ProcessResponse[Output[json.RawMessage]], errors.Error) {
- catalogCmd := "--catalog"
- if t.IsLegacy {
- catalogCmd = "--properties"
- }
- args := []string{"--config", t.configFile.path, catalogCmd, t.propertiesFile.path}
- if t.stateFile != nil {
- args = append(args, []string{"--state", t.stateFile.path}...)
- }
- cmd := exec.Command(t.cmd, args...)
+ cmd := utils.CreateCmd(
+ t.cmd,
+ "--config",
+ t.configFile.path,
+ ifElse(t.IsLegacy, "--properties", "--catalog"),
+ t.propertiesFile.path,
+ ifElse(t.stateFile.path != "", "--state "+t.stateFile.path, ""),
+ )
stream, err := utils.StreamProcess(cmd, func(b []byte) (Output[json.RawMessage], error) {
var output Output[json.RawMessage]
output, err := NewSingerTapOutput(b)
@@ -196,15 +196,14 @@ func hash(x any) (uint64, errors.Error) {
return version, nil
}
-var _ Tap[SingerTapStream] = (*SingerTap)(nil)
-
func (t *SingerTap) modifyProperties(streamName string, propsModifier func(props *SingerTapStream) bool) *SingerTapStream {
properties := t.propertiesFile.content
for i := 0; i < len(properties.Streams); i++ {
stream := properties.Streams[i]
- if !defaultSingerStreamSetter(streamName, stream) {
+ if stream.Stream != streamName {
continue
}
+ setSingerStream(stream)
if propsModifier != nil && propsModifier(stream) {
return stream
}
@@ -212,13 +211,19 @@ func (t *SingerTap) modifyProperties(streamName string, propsModifier func(props
return nil
}
-func defaultSingerStreamSetter(name string, stream *SingerTapStream) bool {
- if stream.Stream != name {
- return false
- }
+func setSingerStream(stream *SingerTapStream) {
for _, meta := range stream.Metadata {
innerMeta := meta["metadata"].(map[string]any)
innerMeta["selected"] = true
}
- return true
}
+
+// ternary if-else so we can inline
+func ifElse(cond bool, onTrue string, onFalse string) string {
+ if cond {
+ return onTrue
+ }
+ return onFalse
+}
+
+var _ Tap[SingerTapStream] = (*SingerTap)(nil)
diff --git a/helpers/pluginhelper/tap/tap_collector.go b/helpers/pluginhelper/tap/tap_collector.go
index aac67fdef..99aba819d 100644
--- a/helpers/pluginhelper/tap/tap_collector.go
+++ b/helpers/pluginhelper/tap/tap_collector.go
@@ -141,8 +141,10 @@ func (c *Collector[Stream]) Execute() (err errors.Error) {
ctx := c.ctx.GetContext()
var batchedResults []json.RawMessage
defer func() {
- // push whatever is left
- err = c.pushResults(batchedResults)
+ if err == nil {
+ // push whatever is left
+ err = c.pushResults(batchedResults)
+ }
}()
for result := range resultStream {
if result.Err != nil {
diff --git a/plugins/pagerduty/api/blueprint.go b/plugins/pagerduty/api/blueprint.go
new file mode 100644
index 000000000..636e430bb
--- /dev/null
+++ b/plugins/pagerduty/api/blueprint.go
@@ -0,0 +1,64 @@
+/*
+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 (
+ "encoding/json"
+ "github.com/apache/incubator-devlake/errors"
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/apache/incubator-devlake/plugins/helper"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/tasks"
+)
+
+func MakePipelinePlan(subtaskMetas []core.SubTaskMeta, connectionId uint64, scope []*core.BlueprintScopeV100) (core.PipelinePlan, errors.Error) {
+ var err errors.Error
+ plan := make(core.PipelinePlan, len(scope))
+ for i, scopeElem := range scope {
+ taskOptions := make(map[string]interface{})
+ err = errors.Convert(json.Unmarshal(scopeElem.Options, &taskOptions))
+ if err != nil {
+ return nil, errors.Default.Wrap(err, "error unmarshalling task options")
+ }
+ var transformationRules tasks.TransformationRules
+ if len(scopeElem.Transformation) > 0 {
+ err = errors.Convert(json.Unmarshal(scopeElem.Transformation, &transformationRules))
+ if err != nil {
+ return nil, errors.Default.Wrap(err, "unable to unmarshal transformation rule")
+ }
+ }
+ taskOptions["connectionId"] = connectionId
+ taskOptions["transformationRules"] = transformationRules
+ _, err = tasks.DecodeAndValidateTaskOptions(taskOptions)
+ if err != nil {
+ return nil, err
+ }
+ // subtasks
+ subtasks, err := helper.MakePipelinePlanSubtasks(subtaskMetas, scopeElem.Entities)
+ if err != nil {
+ return nil, err
+ }
+ plan[i] = core.PipelineStage{
+ {
+ Plugin: "pagerduty",
+ Subtasks: subtasks,
+ Options: taskOptions,
+ },
+ }
+ }
+ return plan, nil
+}
diff --git a/plugins/pagerduty/api/connection.go b/plugins/pagerduty/api/connection.go
new file mode 100644
index 000000000..560235397
--- /dev/null
+++ b/plugins/pagerduty/api/connection.go
@@ -0,0 +1,150 @@
+/*
+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 (
+ "context"
+ "fmt"
+ "github.com/apache/incubator-devlake/errors"
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/apache/incubator-devlake/plugins/helper"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/models"
+ "net/http"
+ "time"
+)
+
+// @Summary test pagerduty connection
+// @Description Test Pagerduty Connection
+// @Tags plugins/pagerduty
+// @Param body body models.TestConnectionRequest true "json body"
+// @Success 200 {object} shared.ApiBody "Success"
+// @Failure 400 {string} errcode.Error "Bad Request"
+// @Failure 500 {string} errcode.Error "Internal Error"
+// @Router /plugins/pagerduty/test [POST]
+func TestConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
+ var params models.TestConnectionRequest
+ err := helper.Decode(input.Body, ¶ms, vld)
+ if err != nil {
+ return nil, err
+ }
+ apiClient, err := helper.NewApiClient(
+ context.TODO(),
+ params.Endpoint,
+ map[string]string{
+ "Authorization": fmt.Sprintf("Token token=%s", params.Token),
+ },
+ 3*time.Second,
+ params.Proxy,
+ basicRes,
+ )
+ if err != nil {
+ return nil, err
+ }
+ response, err := apiClient.Get("licenses", nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ if response.StatusCode == http.StatusOK {
+ return &core.ApiResourceOutput{Body: nil, Status: http.StatusOK}, nil
+ }
+ return &core.ApiResourceOutput{Body: nil, Status: response.StatusCode}, errors.HttpStatus(response.StatusCode).Wrap(err, "could not validate connection")
+}
+
+// @Summary create pagerduty connection
+// @Description Create Pagerduty connection
+// @Tags plugins/pagerduty
+// @Param body body models.PagerDutyConnection true "json body"
+// @Success 200 {object} models.PagerDutyConnection
+// @Failure 400 {string} errcode.Error "Bad Request"
+// @Failure 500 {string} errcode.Error "Internal Error"
+// @Router /plugins/pagerduty/connections [POST]
+func PostConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
+ connection := &models.PagerDutyConnection{}
+ err := connectionHelper.Create(connection, input)
+ if err != nil {
+ return nil, err
+ }
+ return &core.ApiResourceOutput{Body: connection, Status: http.StatusOK}, nil
+}
+
+// @Summary patch pagerduty connection
+// @Description Patch Pagerduty connection
+// @Tags plugins/pagerduty
+// @Param body body models.PagerDutyConnection true "json body"
+// @Success 200 {object} models.PagerDutyConnection
+// @Failure 400 {string} errcode.Error "Bad Request"
+// @Failure 500 {string} errcode.Error "Internal Error"
+// @Router /plugins/pagerduty/connections/{connectionId} [PATCH]
+func PatchConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
+ connection := &models.PagerDutyConnection{}
+ err := connectionHelper.Patch(connection, input)
+ if err != nil {
+ return nil, err
+ }
+ return &core.ApiResourceOutput{Body: connection, Status: http.StatusOK}, nil
+}
+
+// @Summary delete pagerduty connection
+// @Description Delete Pagerduty connection
+// @Tags plugins/pagerduty
+// @Success 200 {object} models.PagerDutyConnection
+// @Failure 400 {string} errcode.Error "Bad Request"
+// @Failure 500 {string} errcode.Error "Internal Error"
+// @Router /plugins/pagerduty/connections/{connectionId} [DELETE]
+func DeleteConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
+ connection := &models.PagerDutyConnection{}
+ err := connectionHelper.First(connection, input.Params)
+ if err != nil {
+ return nil, err
+ }
+ err = connectionHelper.Delete(connection)
+ return &core.ApiResourceOutput{Body: connection}, err
+}
+
+// @Summary list pagerduty connections
+// @Description List Pagerduty connections
+// @Tags plugins/pagerduty
+// @Success 200 {object} models.PagerDutyConnection
+// @Failure 400 {string} errcode.Error "Bad Request"
+// @Failure 500 {string} errcode.Error "Internal Error"
+// @Router /plugins/pagerduty/connections [GET]
+func ListConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
+ var connections []models.PagerDutyConnection
+ err := connectionHelper.List(&connections)
+ if err != nil {
+ return nil, err
+ }
+
+ return &core.ApiResourceOutput{Body: connections}, nil
+}
+
+// @Summary get pagerduty connection
+// @Description Get Pagerduty connection
+// @Tags plugins/pagerduty
+// @Success 200 {object} models.PagerDutyConnection
+// @Failure 400 {string} errcode.Error "Bad Request"
+// @Failure 500 {string} errcode.Error "Internal Error"
+// @Router /plugins/pagerduty/connections/{connectionId} [GET]
+func GetConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
+ connection := &models.PagerDutyConnection{}
+ err := connectionHelper.First(connection, input.Params)
+ if err != nil {
+ return nil, err
+ }
+ return &core.ApiResourceOutput{Body: connection}, nil
+}
diff --git a/plugins/pagerduty/api/init.go b/plugins/pagerduty/api/init.go
new file mode 100644
index 000000000..6774e1482
--- /dev/null
+++ b/plugins/pagerduty/api/init.go
@@ -0,0 +1,39 @@
+/*
+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/plugins/core"
+ "github.com/apache/incubator-devlake/plugins/helper"
+ "github.com/go-playground/validator/v10"
+ "github.com/spf13/viper"
+ "gorm.io/gorm"
+)
+
+var vld *validator.Validate
+var connectionHelper *helper.ConnectionApiHelper
+var basicRes core.BasicRes
+
+func Init(config *viper.Viper, logger core.Logger, database *gorm.DB) {
+ basicRes = helper.NewDefaultBasicRes(config, logger, database)
+ vld = validator.New()
+ connectionHelper = helper.NewConnectionHelper(
+ basicRes,
+ vld,
+ )
+}
diff --git a/plugins/pagerduty/e2e/incident_test.go b/plugins/pagerduty/e2e/incident_test.go
new file mode 100644
index 000000000..08c2667a4
--- /dev/null
+++ b/plugins/pagerduty/e2e/incident_test.go
@@ -0,0 +1,86 @@
+/*
+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 e2e
+
+import (
+ "github.com/apache/incubator-devlake/helpers/e2ehelper"
+ "github.com/apache/incubator-devlake/models/common"
+ "github.com/apache/incubator-devlake/models/domainlayer/ticket"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/impl"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/models"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/tasks"
+ "testing"
+)
+
+func TestIncidentDataFlow(t *testing.T) {
+ var plugin impl.PagerDuty
+ dataflowTester := e2ehelper.NewDataFlowTester(t, "pagerduty", plugin)
+
+ taskData := &tasks.PagerDutyTaskData{
+ Options: &tasks.PagerDutyOptions{
+ ConnectionId: 1,
+ },
+ }
+
+ // import raw data table
+ dataflowTester.ImportCsvIntoRawTable("./raw_tables/_raw_pagerduty_incidents.csv", "_raw_pagerduty_incidents")
+
+ // verify worklog extraction
+ dataflowTester.FlushTabler(&models.Incident{})
+ dataflowTester.FlushTabler(&models.User{})
+ dataflowTester.FlushTabler(&models.Service{})
+ dataflowTester.FlushTabler(&models.Assignment{})
+ dataflowTester.Subtask(tasks.ExtractIncidentsMeta, taskData)
+ dataflowTester.VerifyTableWithOptions(
+ models.Incident{},
+ e2ehelper.TableOptions{
+ CSVRelPath: "./snapshot_tables/_tool_pagerduty_incidents.csv",
+ IgnoreTypes: []any{common.Model{}},
+ },
+ )
+ dataflowTester.VerifyTableWithOptions(
+ models.User{},
+ e2ehelper.TableOptions{
+ CSVRelPath: "./snapshot_tables/_tool_pagerduty_users.csv",
+ IgnoreTypes: []any{common.Model{}},
+ },
+ )
+ dataflowTester.VerifyTableWithOptions(
+ models.Assignment{},
+ e2ehelper.TableOptions{
+ CSVRelPath: "./snapshot_tables/_tool_pagerduty_assignments.csv",
+ IgnoreTypes: []any{common.Model{}},
+ },
+ )
+ dataflowTester.VerifyTableWithOptions(
+ models.Service{},
+ e2ehelper.TableOptions{
+ CSVRelPath: "./snapshot_tables/_tool_pagerduty_services.csv",
+ IgnoreTypes: []any{common.Model{}},
+ },
+ )
+ dataflowTester.FlushTabler(&ticket.Issue{})
+ dataflowTester.Subtask(tasks.ConvertIncidentsMeta, taskData)
+ dataflowTester.VerifyTableWithOptions(
+ ticket.Issue{},
+ e2ehelper.TableOptions{
+ CSVRelPath: "./snapshot_tables/issues.csv",
+ IgnoreTypes: []any{common.NoPKModel{}},
+ },
+ )
+}
diff --git a/plugins/pagerduty/e2e/raw_tables/_raw_pagerduty_incidents.csv b/plugins/pagerduty/e2e/raw_tables/_raw_pagerduty_incidents.csv
new file mode 100644
index 000000000..19989b120
--- /dev/null
+++ b/plugins/pagerduty/e2e/raw_tables/_raw_pagerduty_incidents.csv
@@ -0,0 +1,4 @@
+id,params,data,url,input,created_at
+1,"{""ConnectionId"":1,""Stream"":""incidents""}","{""incident_number"": 4, ""title"": ""Crash reported"", ""created_at"": ""2022-11-03T06:23:06.000000Z"", ""status"": ""triggered"", ""incident_key"": ""bb60942875634ee6a7fe94ddb51c3a09"", ""service"": {""id"": ""PIKL83L"", ""type"": ""service_reference"", ""summary"": ""DevService"", ""self"": ""https://api.pagerduty.com/services/PIKL83L"", ""html_url"": ""https://keon-test.pagerduty.com/service-directory/PIKL83L""}, ""assignments"": [{" [...]
+2,"{""ConnectionId"":1,""Stream"":""incidents""}","{""incident_number"": 5, ""title"": ""Slow startup"", ""created_at"": ""2022-11-03T06:44:28.000000Z"", ""status"": ""acknowledged"", ""incident_key"": ""d7bc6d39c37e4af8b206a12ff6b05793"", ""service"": {""id"": ""PIKL83L"", ""type"": ""service_reference"", ""summary"": ""DevService"", ""self"": ""https://api.pagerduty.com/services/PIKL83L"", ""html_url"": ""https://keon-test.pagerduty.com/service-directory/PIKL83L""}, ""assignments"": [{ [...]
+3,"{""ConnectionId"":1,""Stream"":""incidents""}","{""incident_number"": 6, ""title"": ""Spamming logs"", ""created_at"": ""2022-11-03T06:45:36.000000Z"", ""status"": ""resolved"", ""incident_key"": ""9f5acd07975e4c57bc717d8d9e066785"", ""service"": {""id"": ""PIKL83L"", ""type"": ""service_reference"", ""summary"": ""DevService"", ""self"": ""https://api.pagerduty.com/services/PIKL83L"", ""html_url"": ""https://keon-test.pagerduty.com/service-directory/PIKL83L""}, ""assignments"": [], " [...]
diff --git a/plugins/pagerduty/e2e/snapshot_tables/_tool_pagerduty_assignments.csv b/plugins/pagerduty/e2e/snapshot_tables/_tool_pagerduty_assignments.csv
new file mode 100644
index 000000000..6acfc18e5
--- /dev/null
+++ b/plugins/pagerduty/e2e/snapshot_tables/_tool_pagerduty_assignments.csv
@@ -0,0 +1,4 @@
+incident_number,user_id,created_at,updated_at,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark,connection_id,assigned_at
+4,P25K520,2022-11-03T07:11:37.415+00:00,2022-11-03T07:11:37.415+00:00,"{""ConnectionId"":1,""Stream"":""incidents""}",_raw_pagerduty_incidents,1,,1,2022-11-03T07:02:36.000+00:00
+4,PQYACO3,2022-11-03T07:11:37.415+00:00,2022-11-03T07:11:37.415+00:00,"{""ConnectionId"":1,""Stream"":""incidents""}",_raw_pagerduty_incidents,1,,1,2022-11-03T06:23:06.000+00:00
+5,PQYACO3,2022-11-03T07:11:37.415+00:00,2022-11-03T07:11:37.415+00:00,"{""ConnectionId"":1,""Stream"":""incidents""}",_raw_pagerduty_incidents,2,,1,2022-11-03T06:44:37.000+00:00
diff --git a/plugins/pagerduty/e2e/snapshot_tables/_tool_pagerduty_incidents.csv b/plugins/pagerduty/e2e/snapshot_tables/_tool_pagerduty_incidents.csv
new file mode 100644
index 000000000..920ff1fe5
--- /dev/null
+++ b/plugins/pagerduty/e2e/snapshot_tables/_tool_pagerduty_incidents.csv
@@ -0,0 +1,4 @@
+connection_id,number,created_at,updated_at,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark,url,service_id,summary,status,urgency,created_date,updated_date
+1,4,2022-11-03T07:11:37.422+00:00,2022-11-03T07:11:37.422+00:00,"{""ConnectionId"":1,""Stream"":""incidents""}",_raw_pagerduty_incidents,1,,https://keon-test.pagerduty.com/incidents/Q3YON8WNWTZMRQ,PIKL83L,[#4] Crash reported,triggered,high,2022-11-03T06:23:06.000+00:00,2022-11-03T07:02:36.000+00:00
+1,5,2022-11-03T07:11:37.422+00:00,2022-11-03T07:11:37.422+00:00,"{""ConnectionId"":1,""Stream"":""incidents""}",_raw_pagerduty_incidents,2,,https://keon-test.pagerduty.com/incidents/Q3CZAU7Q4008QD,PIKL83L,[#5] Slow startup,acknowledged,high,2022-11-03T06:44:28.000+00:00,2022-11-03T06:44:37.000+00:00
+1,6,2022-11-03T07:11:37.422+00:00,2022-11-03T07:11:37.422+00:00,"{""ConnectionId"":1,""Stream"":""incidents""}",_raw_pagerduty_incidents,3,,https://keon-test.pagerduty.com/incidents/Q1OHFWFP3GPXOG,PIKL83L,[#6] Spamming logs,resolved,low,2022-11-03T06:45:36.000+00:00,2022-11-03T06:51:44.000+00:00
diff --git a/plugins/pagerduty/e2e/snapshot_tables/_tool_pagerduty_services.csv b/plugins/pagerduty/e2e/snapshot_tables/_tool_pagerduty_services.csv
new file mode 100644
index 000000000..8f5225257
--- /dev/null
+++ b/plugins/pagerduty/e2e/snapshot_tables/_tool_pagerduty_services.csv
@@ -0,0 +1,2 @@
+connection_id,id,created_at,updated_at,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark,url,name
+1,PIKL83L,2022-11-03T07:11:37.411+00:00,2022-11-03T07:11:37.411+00:00,"{""ConnectionId"":1,""Stream"":""incidents""}",_raw_pagerduty_incidents,3,,https://keon-test.pagerduty.com/service-directory/PIKL83L,DevService
diff --git a/plugins/pagerduty/e2e/snapshot_tables/_tool_pagerduty_users.csv b/plugins/pagerduty/e2e/snapshot_tables/_tool_pagerduty_users.csv
new file mode 100644
index 000000000..25ad6a710
--- /dev/null
+++ b/plugins/pagerduty/e2e/snapshot_tables/_tool_pagerduty_users.csv
@@ -0,0 +1,3 @@
+connection_id,id,created_at,updated_at,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark,url,name
+1,P25K520,2022-11-03T07:11:37.418+00:00,2022-11-03T07:11:37.418+00:00,"{""ConnectionId"":1,""Stream"":""incidents""}",_raw_pagerduty_incidents,1,,https://keon-test.pagerduty.com/users/P25K520,Kian Amini
+1,PQYACO3,2022-11-03T07:11:37.418+00:00,2022-11-03T07:11:37.418+00:00,"{""ConnectionId"":1,""Stream"":""incidents""}",_raw_pagerduty_incidents,2,,https://keon-test.pagerduty.com/users/PQYACO3,Keon Amini
diff --git a/plugins/pagerduty/e2e/snapshot_tables/issues.csv b/plugins/pagerduty/e2e/snapshot_tables/issues.csv
new file mode 100644
index 000000000..e923dbcfe
--- /dev/null
+++ b/plugins/pagerduty/e2e/snapshot_tables/issues.csv
@@ -0,0 +1,4 @@
+id,created_at,updated_at,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark,url,icon_url,issue_key,title,description,epic_key,type,status,original_status,story_point,resolution_date,created_date,updated_date,parent_issue_id,priority,original_estimate_minutes,time_spent_minutes,time_remaining_minutes,creator_id,creator_name,assignee_id,assignee_name,severity,component,deployment_id,lead_time_minutes
+pagerduty:Incident:1:4,2022-11-03T07:11:37.437+00:00,2022-11-03T07:11:37.437+00:00,"{""ConnectionId"":1,""Stream"":""incidents""}",_raw_pagerduty_incidents,1,,https://keon-test.pagerduty.com/incidents/Q3YON8WNWTZMRQ,,4,,[#4] Crash reported,,INCIDENT,TODO,triggered,0,,2022-11-03T06:23:06.000+00:00,2022-11-03T07:02:36.000+00:00,,high,0,0,0,,,P25K520,Kian Amini,,,"",0
+pagerduty:Incident:1:5,2022-11-03T07:11:37.437+00:00,2022-11-03T07:11:37.437+00:00,"{""ConnectionId"":1,""Stream"":""incidents""}",_raw_pagerduty_incidents,2,,https://keon-test.pagerduty.com/incidents/Q3CZAU7Q4008QD,,5,,[#5] Slow startup,,INCIDENT,IN_PROGRESS,acknowledged,0,,2022-11-03T06:44:28.000+00:00,2022-11-03T06:44:37.000+00:00,,high,0,0,0,,,PQYACO3,Keon Amini,,,"",0
+pagerduty:Incident:1:6,2022-11-03T07:11:37.437+00:00,2022-11-03T07:11:37.437+00:00,"{""ConnectionId"":1,""Stream"":""incidents""}",_raw_pagerduty_incidents,3,,https://keon-test.pagerduty.com/incidents/Q1OHFWFP3GPXOG,,6,,[#6] Spamming logs,,INCIDENT,DONE,resolved,0,2022-11-03T06:51:44.000+00:00,2022-11-03T06:45:36.000+00:00,2022-11-03T06:51:44.000+00:00,,low,0,0,0,,,,,,,"",6
diff --git a/plugins/pagerduty/impl/impl.go b/plugins/pagerduty/impl/impl.go
new file mode 100644
index 000000000..dcaabb3e8
--- /dev/null
+++ b/plugins/pagerduty/impl/impl.go
@@ -0,0 +1,148 @@
+/*
+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 impl
+
+import (
+ "fmt"
+ "github.com/apache/incubator-devlake/errors"
+ "github.com/apache/incubator-devlake/helpers/pluginhelper/tap"
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/apache/incubator-devlake/plugins/helper"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/api"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/models"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/models/migrationscripts"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/tasks"
+ "github.com/spf13/viper"
+ "gorm.io/gorm"
+ "time"
+)
+
+// make sure interface is implemented
+var _ core.PluginMeta = (*PagerDuty)(nil)
+var _ core.PluginInit = (*PagerDuty)(nil)
+var _ core.PluginTask = (*PagerDuty)(nil)
+var _ core.PluginApi = (*PagerDuty)(nil)
+var _ core.PluginBlueprintV100 = (*PagerDuty)(nil)
+var _ core.CloseablePluginTask = (*PagerDuty)(nil)
+
+type PagerDuty struct{}
+
+func (plugin PagerDuty) Description() string {
+ return "collect some PagerDuty data"
+}
+
+func (plugin PagerDuty) Init(config *viper.Viper, logger core.Logger, db *gorm.DB) errors.Error {
+ api.Init(config, logger, db)
+ return nil
+}
+
+func (plugin PagerDuty) SubTaskMetas() []core.SubTaskMeta {
+ return []core.SubTaskMeta{
+ tasks.CollectIncidentsMeta,
+ tasks.ExtractIncidentsMeta,
+ tasks.ConvertIncidentsMeta,
+ }
+}
+
+func (plugin PagerDuty) PrepareTaskData(taskCtx core.TaskContext, options map[string]interface{}) (interface{}, errors.Error) {
+ op, err := tasks.DecodeAndValidateTaskOptions(options)
+ if err != nil {
+ return nil, err
+ }
+ connectionHelper := helper.NewConnectionHelper(
+ taskCtx,
+ nil,
+ )
+ connection := &models.PagerDutyConnection{}
+ err = connectionHelper.FirstById(connection, op.ConnectionId)
+ if err != nil {
+ return nil, errors.Default.Wrap(err, "unable to get Pagerduty connection by the given connection ID")
+ }
+ startDate, err := parseTime("start_date", options)
+ if err != nil {
+ return nil, err
+ }
+ config := &models.PagerDutyConfig{
+ Token: connection.Token,
+ Email: "", // ignore, works without it too
+ StartDate: startDate,
+ }
+ tapClient, err := tap.NewSingerTap(&tap.SingerTapConfig{
+ TapExecutable: models.TapExecutable,
+ StreamPropertiesFile: models.StreamPropertiesFile,
+ })
+ if err != nil {
+ return nil, err
+ }
+ return &tasks.PagerDutyTaskData{
+ Options: op,
+ Config: config,
+ Client: tapClient,
+ }, nil
+}
+
+// PkgPath information lost when compiled as plugin(.so)
+func (plugin PagerDuty) RootPkgPath() string {
+ return "github.com/apache/incubator-devlake/plugins/pagerduty"
+}
+
+func (plugin PagerDuty) MigrationScripts() []core.MigrationScript {
+ return migrationscripts.All()
+}
+
+func (plugin PagerDuty) ApiResources() map[string]map[string]core.ApiResourceHandler {
+ return map[string]map[string]core.ApiResourceHandler{
+ "test": {
+ "POST": api.TestConnection,
+ },
+ "connections": {
+ "POST": api.PostConnections,
+ "GET": api.ListConnections,
+ },
+ "connections/:connectionId": {
+ "GET": api.GetConnection,
+ "PATCH": api.PatchConnection,
+ "DELETE": api.DeleteConnection,
+ },
+ }
+}
+
+func (plugin PagerDuty) MakePipelinePlan(connectionId uint64, scope []*core.BlueprintScopeV100) (core.PipelinePlan, errors.Error) {
+ return api.MakePipelinePlan(plugin.SubTaskMetas(), connectionId, scope)
+}
+
+func (plugin PagerDuty) Close(taskCtx core.TaskContext) errors.Error {
+ _, ok := taskCtx.GetData().(*tasks.PagerDutyTaskData)
+ if !ok {
+ return errors.Default.New(fmt.Sprintf("GetData failed when try to close %+v", taskCtx))
+ }
+ return nil
+}
+
+func parseTime(key string, opts map[string]any) (time.Time, errors.Error) {
+ var date time.Time
+ dateRaw, ok := opts[key]
+ if !ok {
+ return date, errors.BadInput.New("time input not provided")
+ }
+ date, err := time.Parse("2006-01-02T15:04:05Z", dateRaw.(string))
+ if err != nil {
+ return date, errors.BadInput.Wrap(err, "bad type input provided")
+ }
+ return date, nil
+}
diff --git a/plugins/pagerduty/models/assignment.go b/plugins/pagerduty/models/assignment.go
new file mode 100644
index 000000000..60d1875ce
--- /dev/null
+++ b/plugins/pagerduty/models/assignment.go
@@ -0,0 +1,35 @@
+/*
+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 models
+
+import (
+ "github.com/apache/incubator-devlake/models/common"
+ "time"
+)
+
+type Assignment struct {
+ common.NoPKModel
+ ConnectionId uint64
+ UserId string `gorm:"primaryKey"`
+ IncidentNumber int `gorm:"primaryKey"`
+ AssignedAt time.Time
+}
+
+func (Assignment) TableName() string {
+ return "_tool_pagerduty_assignments"
+}
diff --git a/plugins/pagerduty/models/config.go b/plugins/pagerduty/models/config.go
new file mode 100644
index 000000000..c1818dc92
--- /dev/null
+++ b/plugins/pagerduty/models/config.go
@@ -0,0 +1,34 @@
+/*
+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 models
+
+import (
+ "time"
+)
+
+// PagerDutyConfig model corresponds to docs here https://github.com/singer-io/tap-pagerduty
+type PagerDutyConfig struct {
+ Token string `json:"token"`
+ Email string `json:"email"` // Seems to be an inconsequential field
+ StartDate time.Time `json:"start_date"`
+}
+
+type PagerDutyParams struct {
+ ConnectionId uint64
+ Stream string
+}
diff --git a/plugins/pagerduty/models/connection.go b/plugins/pagerduty/models/connection.go
new file mode 100644
index 000000000..82e79823c
--- /dev/null
+++ b/plugins/pagerduty/models/connection.go
@@ -0,0 +1,52 @@
+/*
+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 models
+
+import (
+ "github.com/apache/incubator-devlake/plugins/helper"
+)
+
+// TODO Please modify the following code to fit your needs
+// This object conforms to what the frontend currently sends.
+type PagerDutyConnection struct {
+ helper.BaseConnection `mapstructure:",squash"`
+ helper.AccessToken `mapstructure:",squash"`
+}
+
+type TestConnectionRequest struct {
+ Endpoint string `json:"endpoint" validate:"required,url"`
+ Token string `json:"token" validate:"required"`
+ Proxy string `json:"proxy"`
+}
+
+// This object conforms to what the frontend currently expects.
+type PagerDutyResponse struct {
+ Name string `json:"name"`
+ ID int `json:"id"`
+ PagerDutyConnection
+}
+
+// Using User because it requires authentication.
+type ApiUserResponse struct {
+ Id int
+ Name string `json:"name"`
+}
+
+func (PagerDutyConnection) TableName() string {
+ return "_tool_pagerduty_connections"
+}
diff --git a/plugins/pagerduty/models/consts.go b/plugins/pagerduty/models/consts.go
new file mode 100644
index 000000000..2d2c658c5
--- /dev/null
+++ b/plugins/pagerduty/models/consts.go
@@ -0,0 +1,25 @@
+/*
+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 models
+
+// The consts that this plugin needs
+const (
+ TapExecutable = "tap-pagerduty"
+ StreamPropertiesFile = "pagerduty.json"
+ IncidentStream = "incidents"
+)
diff --git a/plugins/pagerduty/models/generated/incidents.go b/plugins/pagerduty/models/generated/incidents.go
new file mode 100644
index 000000000..04e0941d8
--- /dev/null
+++ b/plugins/pagerduty/models/generated/incidents.go
@@ -0,0 +1,491 @@
+/*
+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.
+*/
+// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.
+
+package generated
+
+import "time"
+
+type Incidents struct {
+ // Acknowledgements corresponds to the JSON schema field "acknowledgements".
+ Acknowledgements []IncidentsAcknowledgementsElem `json:"acknowledgements,omitempty"`
+
+ // Alerts corresponds to the JSON schema field "alerts".
+ Alerts []IncidentsAlertsElem `json:"alerts,omitempty"`
+
+ // Assignments corresponds to the JSON schema field "assignments".
+ Assignments []IncidentsAssignmentsElem `json:"assignments,omitempty"`
+
+ // ConferenceBridge corresponds to the JSON schema field "conference_bridge".
+ ConferenceBridge *IncidentsConferenceBridge `json:"conference_bridge,omitempty"`
+
+ // CreatedAt corresponds to the JSON schema field "created_at".
+ CreatedAt *time.Time `json:"created_at,omitempty"`
+
+ // EscalationPolicy corresponds to the JSON schema field "escalation_policy".
+ EscalationPolicy *IncidentsEscalationPolicy `json:"escalation_policy,omitempty"`
+
+ // FirstTriggerLogEntry corresponds to the JSON schema field
+ // "first_trigger_log_entry".
+ FirstTriggerLogEntry *IncidentsFirstTriggerLogEntry `json:"first_trigger_log_entry,omitempty"`
+
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // IncidentKey corresponds to the JSON schema field "incident_key".
+ IncidentKey *string `json:"incident_key,omitempty"`
+
+ // IncidentNumber corresponds to the JSON schema field "incident_number".
+ IncidentNumber *int `json:"incident_number,omitempty"`
+
+ // LastStatusChangeAt corresponds to the JSON schema field
+ // "last_status_change_at".
+ LastStatusChangeAt *time.Time `json:"last_status_change_at,omitempty"`
+
+ // LastStatusChangeBy corresponds to the JSON schema field
+ // "last_status_change_by".
+ LastStatusChangeBy *IncidentsLastStatusChangeBy `json:"last_status_change_by,omitempty"`
+
+ // LogEntries corresponds to the JSON schema field "log_entries".
+ LogEntries []IncidentsLogEntriesElem `json:"log_entries,omitempty"`
+
+ // Priority corresponds to the JSON schema field "priority".
+ Priority *IncidentsPriority `json:"priority,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Service corresponds to the JSON schema field "service".
+ Service *IncidentsService `json:"service,omitempty"`
+
+ // Status corresponds to the JSON schema field "status".
+ Status *string `json:"status,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Teams corresponds to the JSON schema field "teams".
+ Teams []IncidentsTeamsElem `json:"teams,omitempty"`
+
+ // Title corresponds to the JSON schema field "title".
+ Title *string `json:"title,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+
+ // Urgency corresponds to the JSON schema field "urgency".
+ Urgency *string `json:"urgency,omitempty"`
+}
+
+type IncidentsAcknowledgementsElem struct {
+ // Acknowledger corresponds to the JSON schema field "acknowledger".
+ Acknowledger *IncidentsAcknowledgementsElemAcknowledger `json:"acknowledger,omitempty"`
+
+ // At corresponds to the JSON schema field "at".
+ At *time.Time `json:"at,omitempty"`
+}
+
+type IncidentsAcknowledgementsElemAcknowledger struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsAlertsElem struct {
+ // AlertKey corresponds to the JSON schema field "alert_key".
+ AlertKey *string `json:"alert_key,omitempty"`
+
+ // Body corresponds to the JSON schema field "body".
+ Body *IncidentsAlertsElemBody `json:"body,omitempty"`
+
+ // CreatedAt corresponds to the JSON schema field "created_at".
+ CreatedAt *time.Time `json:"created_at,omitempty"`
+
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Incident corresponds to the JSON schema field "incident".
+ Incident *IncidentsAlertsElemIncident `json:"incident,omitempty"`
+
+ // Integration corresponds to the JSON schema field "integration".
+ Integration *IncidentsAlertsElemIntegration `json:"integration,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Service corresponds to the JSON schema field "service".
+ Service *IncidentsAlertsElemService `json:"service,omitempty"`
+
+ // Severity corresponds to the JSON schema field "severity".
+ Severity *string `json:"severity,omitempty"`
+
+ // Status corresponds to the JSON schema field "status".
+ Status *string `json:"status,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Suppressed corresponds to the JSON schema field "suppressed".
+ Suppressed *bool `json:"suppressed,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsAlertsElemBody struct {
+ // Contexts corresponds to the JSON schema field "contexts".
+ Contexts []IncidentsAlertsElemBodyContextsElem `json:"contexts,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsAlertsElemBodyContextsElem struct {
+ // Href corresponds to the JSON schema field "href".
+ Href *string `json:"href,omitempty"`
+
+ // Src corresponds to the JSON schema field "src".
+ Src *string `json:"src,omitempty"`
+
+ // Text corresponds to the JSON schema field "text".
+ Text *string `json:"text,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsAlertsElemIncident struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsAlertsElemIntegration struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Name corresponds to the JSON schema field "name".
+ Name *string `json:"name,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Service corresponds to the JSON schema field "service".
+ Service *IncidentsAlertsElemIntegrationService `json:"service,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsAlertsElemIntegrationService struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsAlertsElemService struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsAssignmentsElem struct {
+ // Assignee corresponds to the JSON schema field "assignee".
+ Assignee *IncidentsAssignmentsElemAssignee `json:"assignee,omitempty"`
+
+ // At corresponds to the JSON schema field "at".
+ At *time.Time `json:"at,omitempty"`
+}
+
+type IncidentsAssignmentsElemAssignee struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsConferenceBridge struct {
+ // ConferenceNumber corresponds to the JSON schema field "conference_number".
+ ConferenceNumber *string `json:"conference_number,omitempty"`
+
+ // ConferenceUrl corresponds to the JSON schema field "conference_url".
+ ConferenceUrl *string `json:"conference_url,omitempty"`
+}
+
+type IncidentsEscalationPolicy struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsFirstTriggerLogEntry struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsLastStatusChangeBy struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsLogEntriesElem struct {
+ // Agent corresponds to the JSON schema field "agent".
+ Agent *IncidentsLogEntriesElemAgent `json:"agent,omitempty"`
+
+ // Channel corresponds to the JSON schema field "channel".
+ Channel *IncidentsLogEntriesElemChannel `json:"channel,omitempty"`
+
+ // CreatedAt corresponds to the JSON schema field "created_at".
+ CreatedAt *time.Time `json:"created_at,omitempty"`
+
+ // EventDetails corresponds to the JSON schema field "event_details".
+ EventDetails *IncidentsLogEntriesElemEventDetails `json:"event_details,omitempty"`
+
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Incident corresponds to the JSON schema field "incident".
+ Incident *IncidentsLogEntriesElemIncident `json:"incident,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Teams corresponds to the JSON schema field "teams".
+ Teams []IncidentsLogEntriesElemTeamsElem `json:"teams,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsLogEntriesElemAgent struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsLogEntriesElemChannel struct {
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsLogEntriesElemEventDetails struct {
+ // Description corresponds to the JSON schema field "description".
+ Description *string `json:"description,omitempty"`
+}
+
+type IncidentsLogEntriesElemIncident struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsLogEntriesElemTeamsElem struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsPriority struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsService struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type IncidentsTeamsElem struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
diff --git a/plugins/pagerduty/models/generated/notifications.go b/plugins/pagerduty/models/generated/notifications.go
new file mode 100644
index 000000000..721ea5479
--- /dev/null
+++ b/plugins/pagerduty/models/generated/notifications.go
@@ -0,0 +1,55 @@
+/*
+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.
+*/
+// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.
+
+package generated
+
+import "time"
+
+type Notifications struct {
+ // Address corresponds to the JSON schema field "address".
+ Address *string `json:"address,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // StartedAt corresponds to the JSON schema field "started_at".
+ StartedAt *time.Time `json:"started_at,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+
+ // User corresponds to the JSON schema field "user".
+ User *NotificationsUser `json:"user,omitempty"`
+}
+
+type NotificationsUser struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
diff --git a/plugins/pagerduty/models/generated/services.go b/plugins/pagerduty/models/generated/services.go
new file mode 100644
index 000000000..0823548a1
--- /dev/null
+++ b/plugins/pagerduty/models/generated/services.go
@@ -0,0 +1,205 @@
+/*
+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.
+*/
+// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.
+
+package generated
+
+import "time"
+
+type Services struct {
+ // AcknowledgementTimeout corresponds to the JSON schema field
+ // "acknowledgement_timeout".
+ AcknowledgementTimeout *int `json:"acknowledgement_timeout,omitempty"`
+
+ // AlertCreation corresponds to the JSON schema field "alert_creation".
+ AlertCreation *string `json:"alert_creation,omitempty"`
+
+ // AlertGrouping corresponds to the JSON schema field "alert_grouping".
+ AlertGrouping *string `json:"alert_grouping,omitempty"`
+
+ // AlertGroupingTimeout corresponds to the JSON schema field
+ // "alert_grouping_timeout".
+ AlertGroupingTimeout *int `json:"alert_grouping_timeout,omitempty"`
+
+ // AutoResolveTimeout corresponds to the JSON schema field "auto_resolve_timeout".
+ AutoResolveTimeout *int `json:"auto_resolve_timeout,omitempty"`
+
+ // CreatedAt corresponds to the JSON schema field "created_at".
+ CreatedAt *time.Time `json:"created_at,omitempty"`
+
+ // Description corresponds to the JSON schema field "description".
+ Description *string `json:"description,omitempty"`
+
+ // EscalationPolicy corresponds to the JSON schema field "escalation_policy".
+ EscalationPolicy *ServicesEscalationPolicy `json:"escalation_policy,omitempty"`
+
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // IncidentUrgencyRule corresponds to the JSON schema field
+ // "incident_urgency_rule".
+ IncidentUrgencyRule *ServicesIncidentUrgencyRule `json:"incident_urgency_rule,omitempty"`
+
+ // Integrations corresponds to the JSON schema field "integrations".
+ Integrations []ServicesIntegrationsElem `json:"integrations,omitempty"`
+
+ // LastIncidentTimestamp corresponds to the JSON schema field
+ // "last_incident_timestamp".
+ LastIncidentTimestamp *time.Time `json:"last_incident_timestamp,omitempty"`
+
+ // Name corresponds to the JSON schema field "name".
+ Name *string `json:"name,omitempty"`
+
+ // ScheduledActions corresponds to the JSON schema field "scheduled_actions".
+ ScheduledActions []ServicesScheduledActionsElem `json:"scheduled_actions,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Status corresponds to the JSON schema field "status".
+ Status *string `json:"status,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // SupportHours corresponds to the JSON schema field "support_hours".
+ SupportHours *ServicesSupportHours `json:"support_hours,omitempty"`
+
+ // Teams corresponds to the JSON schema field "teams".
+ Teams []ServicesTeamsElem `json:"teams,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type ServicesEscalationPolicy struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type ServicesIncidentUrgencyRule struct {
+ // DuringSupportHours corresponds to the JSON schema field "during_support_hours".
+ DuringSupportHours *ServicesIncidentUrgencyRuleDuringSupportHours `json:"during_support_hours,omitempty"`
+
+ // OutsideSupportHours corresponds to the JSON schema field
+ // "outside_support_hours".
+ OutsideSupportHours *ServicesIncidentUrgencyRuleOutsideSupportHours `json:"outside_support_hours,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type ServicesIncidentUrgencyRuleDuringSupportHours struct {
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+
+ // Urgency corresponds to the JSON schema field "urgency".
+ Urgency *string `json:"urgency,omitempty"`
+}
+
+type ServicesIncidentUrgencyRuleOutsideSupportHours struct {
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+
+ // Urgency corresponds to the JSON schema field "urgency".
+ Urgency *string `json:"urgency,omitempty"`
+}
+
+type ServicesIntegrationsElem struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type ServicesScheduledActionsElem struct {
+ // At corresponds to the JSON schema field "at".
+ At *ServicesScheduledActionsElemAt `json:"at,omitempty"`
+
+ // ToUrgency corresponds to the JSON schema field "to_urgency".
+ ToUrgency *string `json:"to_urgency,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type ServicesScheduledActionsElemAt struct {
+ // Name corresponds to the JSON schema field "name".
+ Name *string `json:"name,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type ServicesSupportHours struct {
+ // DaysOfWeek corresponds to the JSON schema field "days_of_week".
+ DaysOfWeek []int `json:"days_of_week,omitempty"`
+
+ // EndTime corresponds to the JSON schema field "end_time".
+ EndTime *string `json:"end_time,omitempty"`
+
+ // StartTime corresponds to the JSON schema field "start_time".
+ StartTime *string `json:"start_time,omitempty"`
+
+ // TimeZone corresponds to the JSON schema field "time_zone".
+ TimeZone *string `json:"time_zone,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
+
+type ServicesTeamsElem struct {
+ // HtmlUrl corresponds to the JSON schema field "html_url".
+ HtmlUrl *string `json:"html_url,omitempty"`
+
+ // Id corresponds to the JSON schema field "id".
+ Id *string `json:"id,omitempty"`
+
+ // Self corresponds to the JSON schema field "self".
+ Self *string `json:"self,omitempty"`
+
+ // Summary corresponds to the JSON schema field "summary".
+ Summary *string `json:"summary,omitempty"`
+
+ // Type corresponds to the JSON schema field "type".
+ Type *string `json:"type,omitempty"`
+}
diff --git a/plugins/pagerduty/models/incident.go b/plugins/pagerduty/models/incident.go
new file mode 100644
index 000000000..182fa4fab
--- /dev/null
+++ b/plugins/pagerduty/models/incident.go
@@ -0,0 +1,51 @@
+/*
+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 models
+
+import (
+ "github.com/apache/incubator-devlake/models/common"
+ "time"
+)
+
+const (
+ IncidentStatusAcknowledged IncidentStatus = "acknowledged"
+ IncidentStatusTriggered IncidentStatus = "triggered"
+ IncidentStatusResolved IncidentStatus = "resolved"
+)
+
+type (
+ IncidentUrgency string
+ IncidentStatus string
+
+ Incident struct {
+ common.NoPKModel
+ ConnectionId uint64 `gorm:"primaryKey"`
+ Number int `gorm:"primaryKey"`
+ Url string
+ ServiceId string
+ Summary string
+ Status IncidentStatus //acknowledged, triggered, resolved
+ Urgency IncidentUrgency //high or low
+ CreatedDate time.Time
+ UpdatedDate time.Time
+ }
+)
+
+func (Incident) TableName() string {
+ return "_tool_pagerduty_incidents"
+}
diff --git a/plugins/pagerduty/models/migrationscripts/20221115_add_init_tables.go b/plugins/pagerduty/models/migrationscripts/20221115_add_init_tables.go
new file mode 100644
index 000000000..3e228fed1
--- /dev/null
+++ b/plugins/pagerduty/models/migrationscripts/20221115_add_init_tables.go
@@ -0,0 +1,46 @@
+/*
+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/errors"
+ "github.com/apache/incubator-devlake/helpers/migrationhelper"
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/models/migrationscripts/archived"
+)
+
+type addInitTables struct{}
+
+func (*addInitTables) Up(baseRes core.BasicRes) errors.Error {
+ err := migrationhelper.AutoMigrateTables(baseRes,
+ &archived.Connection{},
+ &archived.Incident{},
+ &archived.User{},
+ &archived.Assignment{},
+ &archived.Service{},
+ )
+ return err
+}
+
+func (*addInitTables) Version() uint64 {
+ return 20221115000001
+}
+
+func (*addInitTables) Name() string {
+ return "PagerDuty init schemas"
+}
diff --git a/plugins/pagerduty/models/migrationscripts/archived/assignment.go b/plugins/pagerduty/models/migrationscripts/archived/assignment.go
new file mode 100644
index 000000000..bda07fa2a
--- /dev/null
+++ b/plugins/pagerduty/models/migrationscripts/archived/assignment.go
@@ -0,0 +1,35 @@
+/*
+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 archived
+
+import (
+ "github.com/apache/incubator-devlake/models/common"
+ "time"
+)
+
+type Assignment struct {
+ common.NoPKModel
+ ConnectionId uint64
+ IncidentNumber int `gorm:"primaryKey"`
+ UserId string `gorm:"primaryKey"`
+ AssignedAt time.Time
+}
+
+func (Assignment) TableName() string {
+ return "_tool_pagerduty_assignments"
+}
diff --git a/plugins/pagerduty/models/migrationscripts/archived/connection.go b/plugins/pagerduty/models/migrationscripts/archived/connection.go
new file mode 100644
index 000000000..edc752a2d
--- /dev/null
+++ b/plugins/pagerduty/models/migrationscripts/archived/connection.go
@@ -0,0 +1,30 @@
+/*
+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 archived
+
+import "github.com/apache/incubator-devlake/models/migrationscripts/archived"
+
+type Connection struct {
+ archived.Model
+ Name string `gorm:"type:varchar(100);uniqueIndex" json:"name" validate:"required"`
+ Token string `mapstructure:"token" env:"PAGERDUTY_AUTH" validate:"required" encrypt:"yes"`
+}
+
+func (Connection) TableName() string {
+ return "_tool_pagerduty_connections"
+}
diff --git a/plugins/pagerduty/models/migrationscripts/archived/incident.go b/plugins/pagerduty/models/migrationscripts/archived/incident.go
new file mode 100644
index 000000000..5419c3490
--- /dev/null
+++ b/plugins/pagerduty/models/migrationscripts/archived/incident.go
@@ -0,0 +1,40 @@
+/*
+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 archived
+
+import (
+ "github.com/apache/incubator-devlake/models/common"
+ "time"
+)
+
+type Incident struct {
+ common.NoPKModel
+ ConnectionId uint64 `gorm:"primaryKey"`
+ Number int `gorm:"primaryKey"`
+ Url string
+ ServiceId string
+ Summary string
+ Status string
+ Urgency string
+ CreatedDate time.Time
+ UpdatedDate time.Time
+}
+
+func (Incident) TableName() string {
+ return "_tool_pagerduty_incidents"
+}
diff --git a/plugins/pagerduty/models/migrationscripts/archived/service.go b/plugins/pagerduty/models/migrationscripts/archived/service.go
new file mode 100644
index 000000000..759f0a3fa
--- /dev/null
+++ b/plugins/pagerduty/models/migrationscripts/archived/service.go
@@ -0,0 +1,34 @@
+/*
+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 archived
+
+import "github.com/apache/incubator-devlake/models/common"
+
+type (
+ Service struct {
+ common.NoPKModel
+ ConnectionId uint64 `gorm:"primaryKey"`
+ Id string `gorm:"primaryKey"`
+ Url string
+ Name string
+ }
+)
+
+func (Service) TableName() string {
+ return "_tool_pagerduty_services"
+}
diff --git a/plugins/pagerduty/models/migrationscripts/archived/user.go b/plugins/pagerduty/models/migrationscripts/archived/user.go
new file mode 100644
index 000000000..b67771e06
--- /dev/null
+++ b/plugins/pagerduty/models/migrationscripts/archived/user.go
@@ -0,0 +1,32 @@
+/*
+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 archived
+
+import "github.com/apache/incubator-devlake/models/common"
+
+type User struct {
+ common.NoPKModel
+ ConnectionId uint64 `gorm:"primaryKey"`
+ Id string `gorm:"primaryKey"`
+ Url string
+ Name string
+}
+
+func (User) TableName() string {
+ return "_tool_pagerduty_users"
+}
diff --git a/plugins/pagerduty/models/migrationscripts/register.go b/plugins/pagerduty/models/migrationscripts/register.go
new file mode 100644
index 000000000..c0c766cd6
--- /dev/null
+++ b/plugins/pagerduty/models/migrationscripts/register.go
@@ -0,0 +1,29 @@
+/*
+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/plugins/core"
+)
+
+// All return all the migration scripts
+func All() []core.MigrationScript {
+ return []core.MigrationScript{
+ new(addInitTables),
+ }
+}
diff --git a/plugins/pagerduty/models/service.go b/plugins/pagerduty/models/service.go
new file mode 100644
index 000000000..f09dfbe30
--- /dev/null
+++ b/plugins/pagerduty/models/service.go
@@ -0,0 +1,32 @@
+/*
+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 models
+
+import "github.com/apache/incubator-devlake/models/common"
+
+type Service struct {
+ common.NoPKModel
+ ConnectionId uint64 `gorm:"primaryKey"`
+ Url string
+ Id string `gorm:"primaryKey"`
+ Name string
+}
+
+func (Service) TableName() string {
+ return "_tool_pagerduty_services"
+}
diff --git a/plugins/pagerduty/models/user.go b/plugins/pagerduty/models/user.go
new file mode 100644
index 000000000..4af5e059a
--- /dev/null
+++ b/plugins/pagerduty/models/user.go
@@ -0,0 +1,32 @@
+/*
+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 models
+
+import "github.com/apache/incubator-devlake/models/common"
+
+type User struct {
+ common.NoPKModel
+ ConnectionId uint64 `gorm:"primaryKey"`
+ Id string `gorm:"primaryKey"`
+ Url string
+ Name string
+}
+
+func (User) TableName() string {
+ return "_tool_pagerduty_users"
+}
diff --git a/plugins/pagerduty/pager_duty.go b/plugins/pagerduty/pager_duty.go
new file mode 100644
index 000000000..a4bade7f3
--- /dev/null
+++ b/plugins/pagerduty/pager_duty.go
@@ -0,0 +1,43 @@
+/*
+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 main
+
+import (
+ "github.com/apache/incubator-devlake/plugins/pagerduty/impl"
+ "github.com/apache/incubator-devlake/runner"
+ "github.com/spf13/cobra"
+)
+
+// PluginEntry Export a variable named PluginEntry for Framework to search and load
+var PluginEntry impl.PagerDuty //nolint
+
+// standalone mode for debugging
+func main() {
+ cmd := &cobra.Command{Use: "pagerduty"}
+
+ // TODO add your cmd flag if necessary
+ // yourFlag := cmd.Flags().IntP("yourFlag", "y", 8, "TODO add description here")
+ // _ = cmd.MarkFlagRequired("yourFlag")
+
+ cmd.Run = func(cmd *cobra.Command, args []string) {
+ runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{
+ // TODO add more custom params here
+ })
+ }
+ runner.RunCmd(cmd)
+}
diff --git a/plugins/pagerduty/tasks/incidents_collector.go b/plugins/pagerduty/tasks/incidents_collector.go
new file mode 100644
index 000000000..48006c2f7
--- /dev/null
+++ b/plugins/pagerduty/tasks/incidents_collector.go
@@ -0,0 +1,62 @@
+/*
+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 tasks
+
+import (
+ "github.com/apache/incubator-devlake/errors"
+ "github.com/apache/incubator-devlake/helpers/pluginhelper/tap"
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/apache/incubator-devlake/plugins/helper"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/models"
+)
+
+const RAW_INCIDENTS_TABLE = "pagerduty_incidents"
+
+var _ core.SubTaskEntryPoint = CollectIncidents
+
+func CollectIncidents(taskCtx core.SubTaskContext) errors.Error {
+ data := taskCtx.GetData().(*PagerDutyTaskData)
+ collector, err := tap.NewTapCollector(
+ &tap.CollectorArgs[tap.SingerTapStream]{
+ RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+ Ctx: taskCtx,
+ Table: RAW_INCIDENTS_TABLE,
+ Params: models.PagerDutyParams{
+ Stream: models.IncidentStream,
+ ConnectionId: data.Options.ConnectionId,
+ },
+ },
+ TapClient: data.Client,
+ TapConfig: data.Config,
+ ConnectionId: data.Options.ConnectionId, // Seems to be an inconsequential field
+ StreamName: models.IncidentStream,
+ },
+ )
+ if err != nil {
+ return err
+ }
+ return collector.Execute()
+}
+
+var CollectIncidentsMeta = core.SubTaskMeta{
+ Name: "collectIncidents",
+ EntryPoint: CollectIncidents,
+ EnabledByDefault: true,
+ Description: "Collect PagerDuty incidents",
+ DomainTypes: []string{core.DOMAIN_TYPE_TICKET},
+}
diff --git a/plugins/pagerduty/tasks/incidents_converter.go b/plugins/pagerduty/tasks/incidents_converter.go
new file mode 100644
index 000000000..ec13ec2aa
--- /dev/null
+++ b/plugins/pagerduty/tasks/incidents_converter.go
@@ -0,0 +1,143 @@
+/*
+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 tasks
+
+import (
+ "fmt"
+ "github.com/apache/incubator-devlake/errors"
+ "github.com/apache/incubator-devlake/models/common"
+ "github.com/apache/incubator-devlake/models/domainlayer"
+ "github.com/apache/incubator-devlake/models/domainlayer/didgen"
+ "github.com/apache/incubator-devlake/models/domainlayer/ticket"
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/apache/incubator-devlake/plugins/core/dal"
+ "github.com/apache/incubator-devlake/plugins/helper"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/models"
+ "reflect"
+ "time"
+)
+
+var ConvertIncidentsMeta = core.SubTaskMeta{
+ Name: "convertIncidents",
+ EntryPoint: ConvertIncidents,
+ EnabledByDefault: true,
+ Description: "Convert incidents into domain layer table issues",
+ DomainTypes: []string{core.DOMAIN_TYPE_TICKET},
+}
+
+type (
+ // IncidentWithUser struct that represents the joined query result
+ IncidentWithUser struct {
+ common.NoPKModel
+ *models.Incident
+ *models.User
+ AssignedAt time.Time
+ }
+)
+
+func ConvertIncidents(taskCtx core.SubTaskContext) errors.Error {
+ db := taskCtx.GetDal()
+ data := taskCtx.GetData().(*PagerDutyTaskData)
+ cursor, err := db.Cursor(
+ dal.Select("pi.*, pu.*, pa.assigned_at"),
+ dal.From("_tool_pagerduty_incidents AS pi"),
+ dal.Join(`LEFT JOIN _tool_pagerduty_assignments AS pa ON pa.incident_number = pi.number`),
+ dal.Join(`LEFT JOIN _tool_pagerduty_users AS pu ON pa.user_id = pu.id`),
+ dal.Where("pi.connection_id = ?", data.Options.ConnectionId),
+ )
+ if err != nil {
+ return err
+ }
+ defer cursor.Close()
+ seenIncidents := map[int]*IncidentWithUser{}
+ idGen := didgen.NewDomainIdGenerator(&models.Incident{})
+ converter, err := helper.NewDataConverter(helper.DataConverterArgs{
+ RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+ Ctx: taskCtx,
+ Params: models.PagerDutyParams{
+ ConnectionId: data.Options.ConnectionId,
+ Stream: models.IncidentStream,
+ },
+ Table: RAW_INCIDENTS_TABLE,
+ },
+ InputRowType: reflect.TypeOf(IncidentWithUser{}),
+ Input: cursor,
+ Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
+ combined := inputRow.(*IncidentWithUser)
+ incident := combined.Incident
+ user := combined.User
+ if seen, ok := seenIncidents[incident.Number]; ok {
+ if combined.AssignedAt.Before(seen.AssignedAt) {
+ // skip this one (it's an older assignee)
+ return nil, nil
+ }
+ }
+ status := getStatus(incident)
+ leadTime, resolutionDate := getTimes(incident)
+ domainIssue := &ticket.Issue{
+ DomainEntity: domainlayer.DomainEntity{
+ Id: idGen.Generate(data.Options.ConnectionId, incident.Number),
+ },
+ Url: incident.Url,
+ IssueKey: fmt.Sprintf("%d", incident.Number),
+ Description: incident.Summary,
+ Type: ticket.INCIDENT,
+ Status: status,
+ OriginalStatus: string(incident.Status),
+ ResolutionDate: resolutionDate,
+ CreatedDate: &incident.CreatedDate,
+ UpdatedDate: &incident.UpdatedDate,
+ LeadTimeMinutes: leadTime,
+ Priority: string(incident.Urgency),
+ AssigneeId: user.Id,
+ AssigneeName: user.Name,
+ }
+ seenIncidents[incident.Number] = combined
+ return []interface{}{
+ domainIssue,
+ }, nil
+ },
+ })
+ if err != nil {
+ return err
+ }
+ return converter.Execute()
+}
+
+func getStatus(incident *models.Incident) string {
+ if incident.Status == models.IncidentStatusTriggered {
+ return ticket.TODO
+ }
+ if incident.Status == models.IncidentStatusAcknowledged {
+ return ticket.IN_PROGRESS
+ }
+ if incident.Status == models.IncidentStatusResolved {
+ return ticket.DONE
+ }
+ panic("unknown incident status encountered")
+}
+
+func getTimes(incident *models.Incident) (int64, *time.Time) {
+ var leadTime int64
+ var resolutionDate *time.Time
+ if incident.Status == models.IncidentStatusResolved {
+ resolutionDate = &incident.UpdatedDate
+ leadTime = int64(resolutionDate.Sub(incident.CreatedDate).Minutes())
+ }
+ return leadTime, resolutionDate
+}
diff --git a/plugins/pagerduty/tasks/incidents_extractor.go b/plugins/pagerduty/tasks/incidents_extractor.go
new file mode 100644
index 000000000..58152b1a3
--- /dev/null
+++ b/plugins/pagerduty/tasks/incidents_extractor.go
@@ -0,0 +1,107 @@
+/*
+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 tasks
+
+import (
+ "encoding/json"
+ "github.com/apache/incubator-devlake/errors"
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/apache/incubator-devlake/plugins/helper"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/models"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/models/generated"
+)
+
+var _ core.SubTaskEntryPoint = ExtractIncidents
+
+func ExtractIncidents(taskCtx core.SubTaskContext) errors.Error {
+ data := taskCtx.GetData().(*PagerDutyTaskData)
+ extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
+ RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+ Ctx: taskCtx,
+ Params: models.PagerDutyParams{
+ ConnectionId: data.Options.ConnectionId,
+ Stream: models.IncidentStream,
+ },
+ Table: RAW_INCIDENTS_TABLE,
+ },
+ Extract: func(row *helper.RawData) ([]interface{}, errors.Error) {
+ incidentRaw := &generated.Incidents{}
+ err := errors.Convert(json.Unmarshal(row.Data, incidentRaw))
+ if err != nil {
+ return nil, err
+ }
+ results := make([]interface{}, 0, 1)
+ incident := models.Incident{
+ ConnectionId: data.Options.ConnectionId,
+ Number: *incidentRaw.IncidentNumber,
+ Url: *incidentRaw.HtmlUrl,
+ Summary: *incidentRaw.Summary,
+ Status: models.IncidentStatus(*incidentRaw.Status),
+ Urgency: models.IncidentUrgency(*incidentRaw.Urgency),
+ CreatedDate: *incidentRaw.CreatedAt,
+ UpdatedDate: *incidentRaw.LastStatusChangeAt,
+ }
+ results = append(results, &incident)
+ if incidentRaw.Service != nil {
+ service := models.Service{
+ ConnectionId: data.Options.ConnectionId,
+ Url: resolve(incidentRaw.Service.HtmlUrl),
+ Id: *incidentRaw.Service.Id,
+ Name: *incidentRaw.Service.Summary,
+ }
+ incident.ServiceId = service.Id
+ results = append(results, &service)
+ }
+ for _, assignmentRaw := range incidentRaw.Assignments {
+ userRaw := assignmentRaw.Assignee
+ results = append(results, &models.Assignment{
+ ConnectionId: data.Options.ConnectionId,
+ UserId: *userRaw.Id,
+ IncidentNumber: *incidentRaw.IncidentNumber,
+ AssignedAt: *assignmentRaw.At,
+ })
+ results = append(results, &models.User{
+ ConnectionId: data.Options.ConnectionId,
+ Id: *userRaw.Id,
+ Url: resolve(userRaw.HtmlUrl),
+ Name: *userRaw.Summary,
+ })
+ }
+ return results, nil
+ },
+ })
+ if err != nil {
+ return err
+ }
+ return extractor.Execute()
+}
+
+func resolve[T any](t *T) T {
+ if t == nil {
+ return *new(T)
+ }
+ return *t
+}
+
+var ExtractIncidentsMeta = core.SubTaskMeta{
+ Name: "extractIncidents",
+ EntryPoint: ExtractIncidents,
+ EnabledByDefault: true,
+ Description: "Extract PagerDuty incidents",
+ DomainTypes: []string{core.DOMAIN_TYPE_TICKET},
+}
diff --git a/plugins/pagerduty/tasks/task_data.go b/plugins/pagerduty/tasks/task_data.go
new file mode 100644
index 000000000..289caaa58
--- /dev/null
+++ b/plugins/pagerduty/tasks/task_data.go
@@ -0,0 +1,52 @@
+/*
+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 tasks
+
+import (
+ "github.com/apache/incubator-devlake/errors"
+ "github.com/apache/incubator-devlake/helpers/pluginhelper/tap"
+ "github.com/apache/incubator-devlake/plugins/helper"
+ "github.com/apache/incubator-devlake/plugins/pagerduty/models"
+)
+
+type PagerDutyOptions struct {
+ ConnectionId uint64 `json:"connectionId"`
+ Tasks []string `json:"tasks,omitempty"`
+ Transformations TransformationRules
+}
+
+type PagerDutyTaskData struct {
+ Options *PagerDutyOptions `json:"-"`
+ Config *models.PagerDutyConfig
+ Client *tap.SingerTap
+}
+
+type TransformationRules struct {
+ //Placeholder struct for later if needed
+}
+
+func DecodeAndValidateTaskOptions(options map[string]interface{}) (*PagerDutyOptions, errors.Error) {
+ var op PagerDutyOptions
+ if err := helper.Decode(options, &op, nil); err != nil {
+ return nil, err
+ }
+ if op.ConnectionId == 0 {
+ return nil, errors.Default.New("connectionId is invalid")
+ }
+ return &op, nil
+}
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 000000000..a33f6c928
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+setuptools
+wheel
+tap-pagerduty==0.2.0
\ No newline at end of file
diff --git a/scripts/singer-model-generator.sh b/scripts/singer-model-generator.sh
index 79bf04bc5..f5d91938c 100644
--- a/scripts/singer-model-generator.sh
+++ b/scripts/singer-model-generator.sh
@@ -25,60 +25,73 @@ time_format='
}
'
-#======================================================================================
+#===================================== functions =======================================
json_path=$1 # e.g. "./config/singer/github.json"
-tap_stream=$2 # e.g. "issues"
-plugin_path=$3 # e.g. "./plugins/github_singer"
+plugin_path=$2 # e.g. "./plugins/github_singer"
+tap_stream=$3 # e.g. "issues" or "--all" to generate all streams
+
+print_json() {
+ jq -r < "$1" # for debugging
+}
+
+handle_error() {
+ exitcode=$1
+ if [ "$exitcode" != 0 ]; then
+ exit "$exitcode"
+ fi
+}
+
+generate() {
+ tap_stream=$1
+ package="generated"
+ file_name="$tap_stream".go
+ output_path="$plugin_path/models/generated/$file_name"
+ tmp_dir=$(mktemp -d -t schema-XXXXX)
+ json_schema_path="$tmp_dir"/"$tap_stream"
+ # add, as necessary, more elif blocks for additional transformations
+ modified_schema=$(jq --argjson tf "$time_format" '
+ .streams[] |
+ select(.stream=="'"$tap_stream"'").schema |
+ . += { "$schema": "http://json-schema.org/draft-07/schema#" } |
+ walk(
+ if type == "object" and .format == "date-time" then
+ . += { "goJSONSchema": ($tf) }
+ elif "place_holder" == "" then
+ empty
+ else . end
+ )
+ ' < "$json_path")
+ handle_error $?
+ # additional cleanup
+ modified_schema=$(echo "$modified_schema" | sed -r "/\"null\",/d")
+ modified_schema=$(echo "$modified_schema" | sed -r "/.*additionalProperties.*/d")
+ echo "$modified_schema" > "$json_schema_path" &&\
+ gojsonschema -v -p "$package" "$json_schema_path" -o "$output_path"
+ handle_error $?
+ echo "$output_path"
+ # prepend the license text to the generated files
+ cp "$output_path" "$output_path".bak
+ license_header="$(printf "/*\n%s\n*/\n" "$(cat .golangci-goheader.template)")"
+ echo "$license_header" > "$output_path"
+ cat "$output_path".bak >> "$output_path"
+ rm "$output_path".bak
+}
+
+#======================================================================================
if [ $# != 3 ]; then
printf "not enough args. Usage: <json_path> <tap_stream> <output_path>: e.g.\n \"./config/singer/github.json\" \"issues\" \"./plugins/github_singer\"\n"
exit 1
fi
-package="generated"
-file_name="$tap_stream".go
-output_path="$plugin_path/models/generated/$file_name"
-
-tmp_dir=$(mktemp -d -t schema-XXXXX)
-
-json_schema_path="$tmp_dir"/"$tap_stream"
-
-# add, as necessary, more elif blocks for additional transformations
-modified_schema=$(cat "$json_path" | jq --argjson tf "$time_format" '
- .streams[] |
- select(.stream=="'"$tap_stream"'").schema |
- . += { "$schema": "http://json-schema.org/draft-07/schema#" } |
- walk(
- if type == "object" and .format == "date-time" then
- . += { "goJSONSchema": ($tf) }
- elif "place_holder" == "" then
- empty
- else . end
- )
-')
-
-# additional cleanup
-modified_schema=$(echo "$modified_schema" | sed -r "/\"null\",/d")
-modified_schema=$(echo "$modified_schema" | sed -r "/.*additionalProperties.*/d")
-
-echo "$modified_schema" > "$json_schema_path" &&\
-
-# see output
-cat "$json_schema_path" | jq -r
-
-exitcode=$?
-if [ $exitcode != 0 ]; then
- exit $exitcode
+if [ "$tap_stream" = "--all" ]; then
+ for stream in $(jq -r '.streams[].stream' < "$json_path"); do
+ generate "$stream"
+ handle_error $?
+ done
+else
+ generate "$tap_stream"
+ handle_error $?
fi
-gojsonschema -v -p "$package" "$json_schema_path" -o "$output_path"
-
-echo "$output_path"
-
-# prepend the license text to the generated files
-cp "$output_path" "$output_path".bak
-license_header="$(printf "/*\n%s\n/*\n" "$(cat .golangci-goheader.template)")"
-echo "$license_header" > "$output_path"
-cat "$output_path".bak >> "$output_path"
-rm "$output_path".bak
\ No newline at end of file
diff --git a/utils/ipc.go b/utils/ipc.go
index efad7509d..787689fe6 100644
--- a/utils/ipc.go
+++ b/utils/ipc.go
@@ -112,3 +112,21 @@ func StreamProcess[T any](cmd *exec.Cmd, converter func(b []byte) (T, error)) (<
}()
return stream, nil
}
+
+// CreateCmd wraps the args in "sh -c" for shell-level execution
+func CreateCmd(args ...string) *exec.Cmd {
+ if len(args) < 1 {
+ panic("no cmd given")
+ }
+ cmd := "sh"
+ cmdArgs := []string{"-c"}
+ cmdBuilder := &strings.Builder{}
+ for _, elem := range args {
+ if elem != "" {
+ _, _ = cmdBuilder.WriteString(elem)
+ _, _ = cmdBuilder.WriteString(" ")
+ }
+ }
+ cmdArgs = append(cmdArgs, cmdBuilder.String())
+ return exec.Command(cmd, cmdArgs...)
+}