This is an automated email from the ASF dual-hosted git repository.
klesh 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 84fee8a55 fix(ZenTao): add support for non-date string handling in
UnmarshalJSON and introduce related tests (#8589)
84fee8a55 is described below
commit 84fee8a5585a1ac826bfb864eb905bf77fdd7d3f
Author: Bamboo <[email protected]>
AuthorDate: Thu Sep 25 14:16:56 2025 +0800
fix(ZenTao): add support for non-date string handling in UnmarshalJSON and
introduce related tests (#8589)
* fix(jira): update epic collector to use new API endpoint and include all
fields
* fix(jira): enhance epic collector to dynamically select API endpoint
based on JIRA version
* fix(jira): update epic collector to use correct API endpoint for JIRA
Cloud and Server versions
* fix(jira): refactor epic collector to streamline API endpoint selection
and enhance error handling
* fix(jira): fix type for Jira issue descriptions
* refactor(jira): update comment and worklog models to use
FlexibleDescription type for comments
* docs(jira): add ADF reference for FlexibleDescription type in issue model
* refactor(migrations): enhance file meta migration to check column
existence and nullability before modification
* feat(gitextractor): add support for excluding file extensions in commit
stats
* fix(ZenTao): add support for non-date string handling in UnmarshalJSON
and introduce related tests
* Revert "feat(gitextractor): add support for excluding file extensions in
commit stats"
This reverts commit 71b27ba9bfef830370ea68cef2aaf6cb1bea35e3.
* refactor(api): instantiate team and user objects directly in CreateTeam
and CreateUser methods
---
backend/core/models/common/iso8601time.go | 36 ++++++++
backend/core/models/common/iso8601time_test.go | 111 +++++++++++++++++++++++++
backend/plugins/org/api/team.go | 3 +-
backend/plugins/org/api/user.go | 3 +-
4 files changed, 149 insertions(+), 4 deletions(-)
diff --git a/backend/core/models/common/iso8601time.go
b/backend/core/models/common/iso8601time.go
index cc467db48..8b49de208 100644
--- a/backend/core/models/common/iso8601time.go
+++ b/backend/core/models/common/iso8601time.go
@@ -111,6 +111,14 @@ func (jt *Iso8601Time) UnmarshalJSON(b []byte) error {
return nil
}
timeString = strings.Trim(timeString, `"`)
+
+ // Handle special cases for non-standard date representations
+ // Some systems may use text like "长期" (long-term) instead of actual
dates
+ if isNonDateString(timeString) {
+ jt.Time = time.Time{}
+ return nil
+ }
+
t, err := ConvertStringToTime(timeString)
if err != nil {
return err
@@ -119,6 +127,34 @@ func (jt *Iso8601Time) UnmarshalJSON(b []byte) error {
return nil
}
+// isNonDateString checks if a string represents a non-date value like
"long-term"
+func isNonDateString(s string) bool {
+ // Handle various representations of "long-term" in different systems
+ nonDateStrings := []string{
+ "长期", // Chinese for "long-term"
+ "\\u957f\\u671f", // Unicode escape sequence for "长期"
+ "\\\\u957f\\\\u671f", // Double-escaped Unicode sequence
+ "long-term", // English
+ "永久", // Chinese for "permanent"
+ "indefinite", // English
+ "unlimited", // English
+ }
+
+ for _, nonDate := range nonDateStrings {
+ if s == nonDate {
+ return true
+ }
+ }
+
+ // Also check if the string contains the Unicode escape pattern for "长期"
+ // This handles cases where escape sequences might be processed
differently
+ if strings.Contains(s, "957f") && strings.Contains(s, "671f") {
+ return true
+ }
+
+ return false
+}
+
// ToTime FIXME ...
func (jt *Iso8601Time) ToTime() time.Time {
return jt.Time
diff --git a/backend/core/models/common/iso8601time_test.go
b/backend/core/models/common/iso8601time_test.go
index 810d5acca..b984a6632 100644
--- a/backend/core/models/common/iso8601time_test.go
+++ b/backend/core/models/common/iso8601time_test.go
@@ -21,6 +21,7 @@ import (
"database/sql/driver"
"fmt"
"reflect"
+ "strings"
"testing"
"time"
@@ -162,6 +163,116 @@ func TestConvertStringToTime(t *testing.T) {
}
}
+func TestIsNonDateString(t *testing.T) {
+ testCases := []struct {
+ name string
+ input string
+ output bool
+ }{
+ {
+ name: "ZenTao long-term in Chinese",
+ input: "长期",
+ output: true,
+ },
+ {
+ name: "ZenTao long-term in Unicode escape",
+ input: "\\u957f\\u671f",
+ output: true,
+ },
+ {
+ name: "ZenTao long-term in double-escaped Unicode",
+ input: "\\\\u957f\\\\u671f",
+ output: true,
+ },
+ {
+ name: "English long-term",
+ input: "long-term",
+ output: true,
+ },
+ {
+ name: "Chinese permanent",
+ input: "永久",
+ output: true,
+ },
+ {
+ name: "English indefinite",
+ input: "indefinite",
+ output: true,
+ },
+ {
+ name: "English unlimited",
+ input: "unlimited",
+ output: true,
+ },
+ {
+ name: "Valid date string",
+ input: "2023-03-01",
+ output: false,
+ },
+ {
+ name: "Valid datetime string",
+ input: "2023-03-01T12:30:00Z",
+ output: false,
+ },
+ {
+ name: "Random string",
+ input: "random",
+ output: false,
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ output := isNonDateString(tc.input)
+ assert.Equal(t, tc.output, output, "Expected output to
be %v, but got %v", tc.output, output)
+ })
+ }
+}
+
+func TestIso8601Time_UnmarshalJSON_NonDateStrings(t *testing.T) {
+ testCases := []struct {
+ name string
+ input string
+ shouldErr bool
+ }{
+ {
+ name: "ZenTao long-term in Chinese",
+ input: `"长期"`,
+ shouldErr: false,
+ },
+ {
+ name: "ZenTao long-term in Unicode escape",
+ input: `"\\u957f\\u671f"`,
+ shouldErr: false,
+ },
+ {
+ name: "English long-term",
+ input: `"long-term"`,
+ shouldErr: false,
+ },
+ {
+ name: "Valid date",
+ input: `"2023-03-01T12:30:00Z"`,
+ shouldErr: false,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ var iso8601Time Iso8601Time
+ err := iso8601Time.UnmarshalJSON([]byte(tc.input))
+ if tc.shouldErr {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ // For non-date strings, the time should be zero
+ if isNonDateString(strings.Trim(tc.input, `"`))
{
+ assert.True(t,
iso8601Time.Time.IsZero(), "Expected zero time for non-date string")
+ }
+ }
+ })
+ }
+}
+
func TestConvertStringToTimeInLoc(t *testing.T) {
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
diff --git a/backend/plugins/org/api/team.go b/backend/plugins/org/api/team.go
index 36c697a05..133a97e8e 100644
--- a/backend/plugins/org/api/team.go
+++ b/backend/plugins/org/api/team.go
@@ -80,9 +80,8 @@ func (h *Handlers) CreateTeam(input *plugin.ApiResourceInput)
(*plugin.ApiResour
if err != nil {
return nil, err
}
- var t *team
var items []interface{}
- for _, tm := range t.toDomainLayer(tt) {
+ for _, tm := range (&team{}).toDomainLayer(tt) {
items = append(items, tm)
}
err = h.store.deleteAll(&crossdomain.Team{})
diff --git a/backend/plugins/org/api/user.go b/backend/plugins/org/api/user.go
index e4daad412..addd034c7 100644
--- a/backend/plugins/org/api/user.go
+++ b/backend/plugins/org/api/user.go
@@ -79,9 +79,8 @@ func (h *Handlers) CreateUser(input *plugin.ApiResourceInput)
(*plugin.ApiResour
if err != nil {
return nil, err
}
- var u *user
var items []interface{}
- users, teamUsers := u.toDomainLayer(uu)
+ users, teamUsers := (&user{}).toDomainLayer(uu)
for _, user := range users {
items = append(items, user)
}