Skip to content

Commit f61521a

Browse files
committed
fix(jira): _raw_data_*-Spalten in _tool_jira_sprint_reports nachruesten
Integriert den Inhalt von PR apache#9015 in den lokalen Integrations-Branch. Die Sprint-Report-Migration 20260722_add_sprint_report_table.go legt _tool_jira_sprint_reports aus einem Struct an, das common.NoPKModel nicht einbettet, waehrend das Laufzeitmodell models.JiraSprintReport es einbettet. Die Spalten _raw_data_params / _raw_data_table / _raw_data_id / _raw_data_remark (plus created_at, updated_at) fehlten daher, sodass die Cleanup-Query des ApiExtractors WHERE _raw_data_table = ? AND _raw_data_params = ? den Subtask extractSprintReport mit "Error 1054 (42S22): Unknown column '_raw_data_table' in 'where clause'" abbrechen liess. Neue, additive Migration statt Aenderung der alten: Migrationsskripte sind append-only, und ein Edit wuerde Datenbanken nicht reparieren, die die Version bereits protokolliert haben. Zwei Schema-Drift-Guards, die die ECHTEN Migrationsskripte ausfuehren (statt das Laufzeitmodell zu AutoMigrate-n, was genau diese Drift verdecken wuerde): * plugins/jira/e2e/migration_schema_test.go - Jira-spezifisch. * plugins/schema_e2e/migration_schema_test.go - plugin-uebergreifend fuer alle Go-Plugins, inkl. TestAllGoPluginsListed. Der uebergreifende Guard deckte drei bestehende Drifts derselben Klasse auf, je mit eigener additiver Migration behoben: * _tool_taiga_scope_configs - type_mappings fehlte * _tool_teambition_scope_configs - id, created_at, updated_at fehlten * _tool_testmo_scope_configs - connection_id, name fehlten Verifiziert gegen MySQL 8.4.10 und PostgreSQL 17.2: neue Spalten werden nullable angelegt, doppelte NULLs sind im uniqueIndex zulaessig, und die AUTO_INCREMENT-PK laesst sich auf der PK-losen Teambition-Tabelle nachruesten (kein Error 1075). Alle vier Migrationen wurden zusaetzlich auf der bestehenden lokalen Datenbank angewendet. Ausserdem: plugins/schema_e2e in scripts/build-plugins.sh ausgeschlossen (kein Plugin, kein main-Package -> "make build-plugin" scheiterte mit "-buildmode=plugin requires exactly one main package"). AGENTS.md um die Schema-Drift-Guards und die zugehoerigen Fallstricke ergaenzt (fork-only, nicht Teil des Upstream-PRs). Signed-off-by: DoDiODev <DoDiDev@proton.me>
1 parent 2f49d1e commit f61521a

12 files changed

Lines changed: 649 additions & 1 deletion

File tree

AGENTS.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,32 @@ helper.ConnectLocalServer(t, &helper.LocalClientConfig{
162162
### Model Validation
163163
Run `make migration-script-lint` from `backend/` to validate all migration scripts follow the correct format (`YYYYMMDD_description.go`).
164164

165+
### Schema-Drift Guard (migration vs. model)
166+
Two e2e checks assert that the schema produced by the **real migration scripts**
167+
still matches what the **runtime GORM models** expect (they run the migrations
168+
instead of `AutoMigrate`-ing the model, which would hide drift):
169+
- `backend/plugins/jira/e2e/migration_schema_test.go` — Jira-specific regression
170+
test for the Sprint Report `_raw_data_table` bug.
171+
- `backend/plugins/schema_e2e/migration_schema_test.go` — cross-plugin guard for
172+
**every** built-in Go plugin, plus `TestAllGoPluginsListed`, which fails if a
173+
new plugin's `impl` package is not registered in `allGoPlugins()`.
174+
175+
Both require `E2E_DB_URL` and run under `make e2e-test-go-plugins`. A common
176+
cause of failure: a migration struct that forgets to embed `common.NoPKModel`,
177+
so the `_raw_data_*` columns are missing while the model declares them.
178+
179+
Notes:
180+
- `backend/plugins/schema_e2e/` is **not** a plugin (no `main` package); it is
181+
excluded in `backend/scripts/build-plugins.sh`, otherwise `make build-plugin`
182+
fails with "-buildmode=plugin requires exactly one main package".
183+
- Both tests set a fallback `ENCRYPTION_SECRET` and call `dalgorm.Init(...)`;
184+
without it migrations fail with `invalid encKey` / `invalid serializer type
185+
encdec` (`runner.CreateBasicRes` does not register the serializer, only
186+
`CreateAppBasicRes` does).
187+
- Migration scripts must not import `core/models/common` (enforced by
188+
`make migration-script-lint`) — use `core/models/migrationscripts/archived`.
189+
190+
165191
## Python Plugins
166192
Located in `backend/python/plugins/`. Use Poetry for dependencies. See [backend/python/README.md](backend/python/README.md).
167193
Python is pinned to **3.11** (Pydantic v1 + `dbt-mysql` 1.7 block 3.12+).
@@ -177,7 +203,9 @@ Python is pinned to **3.11** (Pydantic v1 + `dbt-mysql` 1.7 block 3.12+).
177203

178204
## Common Pitfalls
179205
- Forgetting to add models to `GetTablesInfo()` fails `plugins/table_info_test.go`
206+
- Migration structs that create a `_tool_*` table must embed `common.NoPKModel` (or `common.RawDataOrigin`) so the `_raw_data_*` columns exist — otherwise the ApiExtractor's cleanup query fails at runtime with `Unknown column '_raw_data_table'`. Guarded by the schema-drift e2e checks (see Testing → Schema-Drift Guard).
180207
- Migration scripts must be added to `All()` in `register.go` AND follow `YYYYMMDD_description.go` naming (validate with `make migration-script-lint`)
208+
- Never edit a migration script that has already shipped/run; fix schema issues with a **new** migration (append-only), since executed versions never re-run.
181209
- API changes require running `make swag` to update Swagger docs (this runs `make mock` first)
182210
- Python plugins require `libgit2` for gitextractor functionality
183211
- New Plugin interfaces like `PluginInit` or `PluginOpenApiSpec` are optional - only implement if needed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package e2e
19+
20+
import (
21+
"sync"
22+
"testing"
23+
24+
"github.com/apache/incubator-devlake/core/dal"
25+
"github.com/apache/incubator-devlake/core/migration"
26+
coreMigration "github.com/apache/incubator-devlake/core/models/migrationscripts"
27+
"github.com/apache/incubator-devlake/core/plugin"
28+
"github.com/apache/incubator-devlake/core/runner"
29+
"github.com/apache/incubator-devlake/helpers/e2ehelper"
30+
"github.com/apache/incubator-devlake/impls/dalgorm"
31+
"github.com/apache/incubator-devlake/plugins/jira/impl"
32+
"github.com/stretchr/testify/assert"
33+
"github.com/stretchr/testify/require"
34+
"gorm.io/gorm/schema"
35+
)
36+
37+
// TestMigrationSchema guards against schema drift between Jira's migration
38+
// scripts and its runtime GORM models.
39+
//
40+
// Regression test for the Sprint Report bug (introduced by PR #8967/#9010):
41+
// the migration that created `_tool_jira_sprint_reports` used a struct that did
42+
// NOT embed common.NoPKModel, so the `_raw_data_table` / `_raw_data_params` /
43+
// `_raw_data_id` / `_raw_data_remark` columns were missing. The runtime model
44+
// DID embed common.NoPKModel, so the ApiExtractor's cleanup query
45+
// (`WHERE _raw_data_table = ? AND _raw_data_params = ?`) failed at runtime with
46+
// "Error 1054: Unknown column '_raw_data_table' in 'where clause'".
47+
//
48+
// The test runs the REAL migration scripts (framework + jira) to build the
49+
// schema exactly the way a production install would — deliberately NOT via
50+
// AutoMigrate on the runtime model, which would silently hide such drift — and
51+
// then asserts that every column each runtime model expects actually exists in
52+
// the migrated table. Any future migration that forgets to embed
53+
// common.NoPKModel (or otherwise omits a column) will fail this test.
54+
//
55+
// Requires E2E_DB_URL (runs under `make e2e-test` / `make e2e-test-go-plugins`).
56+
func TestMigrationSchema(t *testing.T) {
57+
var pluginInstance impl.Jira
58+
dataflowTester := e2ehelper.NewDataFlowTester(t, "jira", pluginInstance)
59+
60+
// Some jira migration scripts refuse to run without an encryption secret
61+
// (20220716: "jira v0.11 invalid encKey"); CI does not necessarily provide
62+
// one, so fall back to a deterministic test value. dalgorm.Init registers
63+
// the `encdec` GORM serializer used by connection models — without it the
64+
// migrations fail with "invalid serializer type encdec".
65+
if dataflowTester.Cfg.GetString(plugin.EncodeKeyEnvStr) == "" {
66+
dataflowTester.Cfg.Set(plugin.EncodeKeyEnvStr, "jira-e2e-test-encryption-secret")
67+
}
68+
dalgorm.Init(dataflowTester.Cfg.GetString(plugin.EncodeKeyEnvStr))
69+
70+
// Apply the real migration scripts so the schema matches a production install.
71+
basicRes := runner.CreateBasicRes(dataflowTester.Cfg, dataflowTester.Log, dataflowTester.Db)
72+
migrator, err := migration.NewMigrator(basicRes)
73+
require.NoError(t, err)
74+
migrator.Register(coreMigration.All(), "Framework")
75+
migrator.Register(pluginInstance.MigrationScripts(), "jira")
76+
require.NoError(t, migrator.Execute())
77+
78+
keepAll := func(dal.ColumnMeta) bool { return true }
79+
80+
for _, table := range pluginInstance.GetTablesInfo() {
81+
table := table
82+
t.Run(table.TableName(), func(t *testing.T) {
83+
// Columns the runtime GORM model expects.
84+
sch, err := schema.Parse(table, &sync.Map{}, schema.NamingStrategy{})
85+
require.NoErrorf(t, err, "unable to parse schema for %T", table)
86+
87+
// Columns that actually exist in the migrated table.
88+
actualColumns, err := dal.GetColumnNames(dataflowTester.Dal, table, keepAll)
89+
if err != nil || len(actualColumns) == 0 {
90+
// No migration creates this table (e.g. API response models
91+
// that are listed in GetTablesInfo but never persisted) —
92+
// there is no schema to drift from.
93+
t.Skipf("table %q not present after migrations", table.TableName())
94+
}
95+
existing := make(map[string]struct{}, len(actualColumns))
96+
for _, c := range actualColumns {
97+
existing[c] = struct{}{}
98+
}
99+
100+
for _, field := range sch.Fields {
101+
if field.DBName == "" || field.IgnoreMigration {
102+
continue
103+
}
104+
_, ok := existing[field.DBName]
105+
assert.Truef(t, ok,
106+
"table %q is missing column %q expected by model %T — "+
107+
"did a migration script forget to embed common.NoPKModel (raw-data columns) or add the field?",
108+
table.TableName(), field.DBName, table)
109+
}
110+
})
111+
}
112+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package migrationscripts
19+
20+
import (
21+
"github.com/apache/incubator-devlake/core/context"
22+
"github.com/apache/incubator-devlake/core/errors"
23+
"github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
24+
"github.com/apache/incubator-devlake/helpers/migrationhelper"
25+
)
26+
27+
// jiraSprintReport20260727 mirrors the JiraSprintReport model. The original
28+
// migration (20260722) created _tool_jira_sprint_reports without embedding
29+
// common.NoPKModel, so the _raw_data_table / _raw_data_params / _raw_data_id /
30+
// _raw_data_remark columns (and created_at / updated_at) were missing. The
31+
// runtime model expects them, which made the ApiExtractor's cleanup query
32+
// (WHERE _raw_data_table = ? AND _raw_data_params = ?) fail with
33+
// "Unknown column '_raw_data_table' in 'where clause'". Re-running
34+
// AutoMigrateTables adds the missing columns without dropping existing data.
35+
type jiraSprintReport20260727 struct {
36+
archived.NoPKModel
37+
ConnectionId uint64 `gorm:"primaryKey"`
38+
BoardId uint64 `gorm:"primaryKey"`
39+
SprintId uint64 `gorm:"primaryKey"`
40+
IssueId uint64 `gorm:"primaryKey"`
41+
42+
IssueKey string `gorm:"type:varchar(255)"`
43+
Bucket string `gorm:"type:varchar(32);index"`
44+
Done bool
45+
StoryPointsAtSprintStart *float64
46+
StoryPointsAtSprintEnd *float64
47+
}
48+
49+
func (jiraSprintReport20260727) TableName() string {
50+
return "_tool_jira_sprint_reports"
51+
}
52+
53+
type addRawDataColumnsToSprintReport struct{}
54+
55+
func (script *addRawDataColumnsToSprintReport) Up(basicRes context.BasicRes) errors.Error {
56+
return migrationhelper.AutoMigrateTables(basicRes, &jiraSprintReport20260727{})
57+
}
58+
59+
func (*addRawDataColumnsToSprintReport) Version() uint64 {
60+
return 20260727000000
61+
}
62+
63+
func (*addRawDataColumnsToSprintReport) Name() string {
64+
return "add missing _raw_data_* columns to _tool_jira_sprint_reports"
65+
}

backend/plugins/jira/models/migrationscripts/register.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,6 @@ func All() []plugin.MigrationScript {
5959
new(changeFixVersionsToText20260707),
6060
new(addExtraJQLToScopeConfig),
6161
new(addSprintReportTable),
62+
new(addRawDataColumnsToSprintReport),
6263
}
6364
}

0 commit comments

Comments
 (0)