From cb27bb8707fcc950c078c4f3af7d1df0e287914c Mon Sep 17 00:00:00 2001 From: sfwn Date: Tue, 10 Oct 2023 04:30:48 -0500 Subject: [PATCH] feat(dop): issue excel title support i18n (#6085) * ExportExcelIssueRequest add ExportType (byFilter/full) * excel title support i18n * remove useless UpdatedAt field in excel * skip unknown common fields * no need to check projectIssueTotalNum, use exportType directly * polish code * fix start error by issue-excel.yaml --- api/proto/dop/issue/core/core.proto | 1 + cmd/erda-server/bootstrap.yaml | 1 + cmd/erda-server/conf/i18n/issue-excel.yaml | 40 ++++++ internal/apps/dop/initialize.go | 1 + .../apps/dop/providers/issue/core/file.go | 23 +--- .../apps/dop/providers/issue/core/provider.go | 5 + .../issue/core/query/issueexcel/export.go | 2 + .../issue/core/query/issueexcel/import.go | 2 + .../issueexcel/sheets/sheet_issue/export.go | 9 +- .../sheets/sheet_issue/export_template.go | 1 - .../issueexcel/sheets/sheet_issue/field.go | 114 ++++++++++++++++++ .../issueexcel/sheets/sheet_issue/import.go | 86 +++++++------ .../sheets/sheet_issue/import_old.go | 50 ++++---- .../sheets/sheet_issue/import_vars.go | 9 +- .../issue/core/query/issueexcel/vars/data.go | 5 +- .../core/query/issueexcel/vars/export_type.go | 21 ++++ .../issue/core/query/issueexcel/vars/i18n.go | 43 +++++++ .../core/query/issueexcel/vars/i18n_test.go | 32 +++++ .../issue/core/query/issueexcel/vars/model.go | 1 - .../apps/dop/providers/issue/core/service.go | 6 + 20 files changed, 362 insertions(+), 90 deletions(-) create mode 100644 cmd/erda-server/conf/i18n/issue-excel.yaml create mode 100644 internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/field.go create mode 100644 internal/apps/dop/providers/issue/core/query/issueexcel/vars/export_type.go create mode 100644 internal/apps/dop/providers/issue/core/query/issueexcel/vars/i18n.go create mode 100644 internal/apps/dop/providers/issue/core/query/issueexcel/vars/i18n_test.go diff --git a/api/proto/dop/issue/core/core.proto b/api/proto/dop/issue/core/core.proto index 95ce0197b37..caa1636d73a 100644 --- a/api/proto/dop/issue/core/core.proto +++ b/api/proto/dop/issue/core/core.proto @@ -994,6 +994,7 @@ message ExportExcelIssueRequest { repeated uint64 projectIDs = 41; string locale = 42; bool isDownloadTemplate = 43 [json_name = "isDownload"]; + string exportType = 44; // byFilter, full } message ExportExcelIssueResponse { diff --git a/cmd/erda-server/bootstrap.yaml b/cmd/erda-server/bootstrap.yaml index 0eda5913a76..e3c28146ff0 100644 --- a/cmd/erda-server/bootstrap.yaml +++ b/cmd/erda-server/bootstrap.yaml @@ -43,6 +43,7 @@ i18n: - conf/i18n/project-pipeline.yaml # dop - conf/i18n/api-management-trans.yaml # dop - conf/i18n/contribution.yaml # dop + - conf/i18n/issue-excel.yaml # dop gorm.v2: host: "${MYSQL_HOST}" diff --git a/cmd/erda-server/conf/i18n/issue-excel.yaml b/cmd/erda-server/conf/i18n/issue-excel.yaml new file mode 100644 index 00000000000..4dabf130ac4 --- /dev/null +++ b/cmd/erda-server/conf/i18n/issue-excel.yaml @@ -0,0 +1,40 @@ +zh: + # Common + Common: 通用字段 + ID: ID + IterationName: 迭代 + IssueType: 类型 + IssueTitle: 标题 + Content: 内容 + State: 状态 + Priority: 优先级 + Complexity: 复杂度 + Severity: 严重程度 + CreatorName: 创建人 + AssigneeName: 处理人 + CreatedAt: 创建时间 + PlanStartedAt: 计划开始时间 + PlanFinishedAt: 计划结束时间 + StartAt: 实际开始时间 + FinishAt: 实际结束时间 + EstimateTime: 预估耗时 + Labels: 标签 + ConnectionIssueIDs: 关联事项ID列表 + CustomFields: 自定义字段 + + # RequirementOnly + RequirementOnly: 需求专属字段 + InclusionIssueIDs: 待办事项ID列表 + + # TaskOnly + TaskOnly: 任务专属字段 + TaskType: 任务类型 + + # BugOnly + BugOnly: 缺陷专属字段 + OwnerName: 负责人 + Source: 来源 + ReopenCount: 重开次数 + +en: # use key directly + Common: Common # need this line to pass i18n provider validation diff --git a/internal/apps/dop/initialize.go b/internal/apps/dop/initialize.go index 9cad368e0c3..84571f6fc63 100644 --- a/internal/apps/dop/initialize.go +++ b/internal/apps/dop/initialize.go @@ -501,6 +501,7 @@ func (p *provider) initEndpoints(db *dao.DBClient) (*endpoints.Endpoints, error) p.IssueCoreSvc.WithTestplan(testPlan) p.IssueCoreSvc.WithTestcase(testCaseSvc) + p.IssueCoreSvc.WithTranslator(p.CPTran) workBench := workbench.New( workbench.WithBundle(bdl.Bdl), diff --git a/internal/apps/dop/providers/issue/core/file.go b/internal/apps/dop/providers/issue/core/file.go index f8472db61ae..e8859fb45ea 100644 --- a/internal/apps/dop/providers/issue/core/file.go +++ b/internal/apps/dop/providers/issue/core/file.go @@ -305,8 +305,9 @@ func (i *IssueService) createDataForFulfillCommon(locale string, userID string, // result dataForFulfill := vars.DataForFulfill{ Bdl: i.bdl, - Locale: i.bdl.GetLocale(locale), ProjectID: projectID, + Tran: i.translator.Translator("issue-excel"), + Lang: vars.GetI18nLang(locale), OrgID: orgID, UserID: userID, StageMap: stageMap, @@ -382,23 +383,9 @@ func (i *IssueService) createDataForFulfillForExport(req *pb.ExportExcelIssueReq return nil, fmt.Errorf("failed to page issues, err: %v", err) } data.ExportOnly.Issues = issues - // get total - _, projectIssueTotalNum, err := i.db.PagingIssues(pb.PagingIssueRequest{ - ProjectID: data.ProjectID, - PageNo: 1, - PageSize: 1, - External: req.External, - OnlyIdResult: true, - }, false) - if err != nil { - return nil, fmt.Errorf("failed to get project issues total num, err: %v", err) - } - if uint64(len(issues)) >= projectIssueTotalNum { - // TODO 前端明确区分是项目迁移还是正常导出 - // 当用户在 UI 上清除所有筛选条件后,'按筛选条件导出' 和 '全量导出' 的差别就在 external 和 orderby 这两个字段 - if req.External == false && req.OrderBy == "" { - data.ExportOnly.IsFullExport = true - } + // 前端明确区分是`按筛选条件导出`还是`全量导出` + if req.ExportType == vars.ExportTypeFull { + data.ExportOnly.IsFullExport = true } data.ExportOnly.IsDownloadTemplate = req.IsDownloadTemplate data.ExportOnly.FileNameWithExt = "issue-export.xlsx" diff --git a/internal/apps/dop/providers/issue/core/provider.go b/internal/apps/dop/providers/issue/core/provider.go index 25e8ae490e5..764ac7c670a 100644 --- a/internal/apps/dop/providers/issue/core/provider.go +++ b/internal/apps/dop/providers/issue/core/provider.go @@ -137,6 +137,11 @@ func (p *provider) Init(ctx servicehub.Context) error { req.OrgID = orgID // all type return same sample req.Type = nil + // locale + lang := apis.HTTPLanguage(r) + if lang.Len() > 0 { + req.Locale = lang[0].String() + } // use new excel export dataForFulfill, err := p.issueService.createDataForFulfillForExport(&req) if err != nil { diff --git a/internal/apps/dop/providers/issue/core/query/issueexcel/export.go b/internal/apps/dop/providers/issue/core/query/issueexcel/export.go index 6da600adcff..c415a482760 100644 --- a/internal/apps/dop/providers/issue/core/query/issueexcel/export.go +++ b/internal/apps/dop/providers/issue/core/query/issueexcel/export.go @@ -49,6 +49,8 @@ func ExportFile(w io.Writer, data *vars.DataForFulfill) (err error) { &sheet_state.Handler{}, } + sheet_issue.InitI18nMap(data) + for _, h := range handlers { // only full export need to export other sheets if h.SheetName() != vars.NameOfSheetIssue { diff --git a/internal/apps/dop/providers/issue/core/query/issueexcel/import.go b/internal/apps/dop/providers/issue/core/query/issueexcel/import.go index 4df3915de3d..0d50f43d4ec 100644 --- a/internal/apps/dop/providers/issue/core/query/issueexcel/import.go +++ b/internal/apps/dop/providers/issue/core/query/issueexcel/import.go @@ -49,6 +49,8 @@ func ImportFile(r io.Reader, data *vars.DataForFulfill) error { &sheet_state.Handler{}, } + sheet_issue.InitI18nMap(data) + for _, h := range handlers { if err := h.ImportSheet(data, df); err != nil { return fmt.Errorf("failed to decode sheet %q, err: %v", h.SheetName(), err) diff --git a/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/export.go b/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/export.go index 0566a9b7b8c..e08b1ae7bbc 100644 --- a/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/export.go +++ b/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/export.go @@ -35,7 +35,7 @@ func (h *Handler) ExportSheet(data *vars.DataForFulfill) (excel.Rows, error) { if err != nil { return nil, fmt.Errorf("failed to gen sheet title and data by column, err: %v", err) } - excelRows, err := mapByColumns.ConvertToExcelSheet() + excelRows, err := mapByColumns.ConvertToExcelSheet(data) if err != nil { return nil, fmt.Errorf("failed to convert to excel sheet, err: %v", err) } @@ -90,11 +90,11 @@ func genIssueSheetTitleAndDataByColumn(data *vars.DataForFulfill) (*IssueSheetMo func getCustomFieldBelongingTypeFromUUID(uuid IssueSheetColumnUUID) pb.IssueTypeEnum_Type { switch uuid.Decode()[0] { - case "RequirementOnly": + case fieldRequirementOnly: return pb.IssueTypeEnum_REQUIREMENT - case "TaskOnly": + case fieldTaskOnly: return pb.IssueTypeEnum_TASK - case "BugOnly": + case fieldBugOnly: return pb.IssueTypeEnum_BUG default: panic(fmt.Errorf("failed to get issue type from uuid: %s", uuid.Decode()[0])) @@ -140,7 +140,6 @@ func getIssueSheetModels(data *vars.DataForFulfill) ([]vars.IssueSheetModel, err CreatorName: getUserNick(data, issue.Creator), AssigneeName: getUserNick(data, issue.Assignee), CreatedAt: pbutil.GetTimeInLocal(issue.CreatedAt), - UpdatedAt: pbutil.GetTimeInLocal(issue.UpdatedAt), PlanStartedAt: pbutil.GetTimeInLocal(issue.PlanStartedAt), PlanFinishedAt: pbutil.GetTimeInLocal(issue.PlanFinishedAt), StartAt: pbutil.GetTimeInLocal(issue.StartTime), diff --git a/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/export_template.go b/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/export_template.go index 3b87e28eb62..c13fc0d4c3b 100644 --- a/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/export_template.go +++ b/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/export_template.go @@ -43,7 +43,6 @@ func GenerateSampleIssueSheetModels(data *vars.DataForFulfill) []vars.IssueSheet CreatorName: data.UserID, AssigneeName: data.UserID, CreatedAt: &now, - UpdatedAt: &now, PlanStartedAt: &now, PlanFinishedAt: &nowPlusOneDay, StartAt: &now, diff --git a/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/field.go b/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/field.go new file mode 100644 index 00000000000..713a88372dc --- /dev/null +++ b/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/field.go @@ -0,0 +1,114 @@ +// Copyright (c) 2021 Terminus, Inc. +// +// Licensed 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 sheet_issue + +import ( + "github.com/erda-project/erda-infra/providers/i18n" + "github.com/erda-project/erda/internal/apps/dop/providers/issue/core/query/issueexcel/vars" +) + +const ( + // Common + fieldCommon = "Common" + fieldID = "ID" + fieldIterationName = "IterationName" + fieldIssueType = "IssueType" + fieldIssueTitle = "IssueTitle" + fieldContent = "Content" + fieldState = "State" + fieldPriority = "Priority" + fieldComplexity = "Complexity" + fieldSeverity = "Severity" + fieldCreatorName = "CreatorName" + fieldAssigneeName = "AssigneeName" + fieldCreatedAt = "CreatedAt" + fieldPlanStartedAt = "PlanStartedAt" + fieldPlanFinishedAt = "PlanFinishedAt" + fieldStartAt = "StartAt" + fieldFinishAt = "FinishAt" + fieldEstimateTime = "EstimateTime" + fieldLabels = "Labels" + fieldConnectionIssueIDs = "ConnectionIssueIDs" + fieldCustomFields = "CustomFields" + + // RequirementOnly + fieldRequirementOnly = "RequirementOnly" + fieldInclusionIssueIDs = "InclusionIssueIDs" + + // TaskOnly + fieldTaskOnly = "TaskOnly" + fieldTaskType = "TaskType" + + // BugOnly + fieldBugOnly = "BugOnly" + fieldOwnerName = "OwnerName" + fieldSource = "Source" + fieldReopenCount = "ReopenCount" +) + +var excelFields = []string{ + // Common + fieldCommon, + fieldID, + fieldIterationName, + fieldIssueType, + fieldIssueTitle, + fieldContent, + fieldState, + fieldPriority, + fieldComplexity, + fieldSeverity, + fieldCreatorName, + fieldAssigneeName, + fieldCreatedAt, + fieldPlanStartedAt, + fieldPlanFinishedAt, + fieldStartAt, + fieldFinishAt, + fieldEstimateTime, + fieldLabels, + fieldConnectionIssueIDs, + fieldCustomFields, + + // RequirementOnly + fieldRequirementOnly, + fieldInclusionIssueIDs, + + // TaskOnly + fieldTaskOnly, + fieldTaskType, + + // BugOnly + fieldBugOnly, + fieldOwnerName, + fieldSource, + fieldReopenCount, +} + +var ( + i18nMapByText = make(map[string]string) +) + +func InitI18nMap(data *vars.DataForFulfill) { + for _, key := range excelFields { + i18nMapByText[key] = key // self + // en-US + enLang, _ := i18n.ParseLanguageCode("en-US") + i18nMapByText[data.Tran.Text(enLang, key)] = key + // zh-CN + zhLang, _ := i18n.ParseLanguageCode("zh-CN") + i18nMapByText[data.Tran.Text(zhLang, key)] = key + } +} diff --git a/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/import.go b/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/import.go index d1e1064e9c4..c1791f61c63 100644 --- a/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/import.go +++ b/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/import.go @@ -70,7 +70,15 @@ func (h *Handler) ImportSheet(data *vars.DataForFulfill, df excel.DecodedFile) e // format like "Common---ID---ID" var uuid IssueSheetColumnUUID for j := 0; j < uuidPartsMustLength; j++ { - uuid.AddPart(issueSheetRows[j][i]) + // parse i18n text to excel field key + rawValue := issueSheetRows[j][i] + cellValue := parseI18nTextToExcelFieldKey(rawValue) + // skip concrete custom field + parts := uuid.Decode() + if len(parts) >= 2 && parts[1] == fieldCustomFields { // it's the third part and part 2 is `CustomFields` + cellValue = rawValue + } + uuid.AddPart(cellValue) } // data rows start from uuidPartsMustLength for k := range issueSheetRows[uuidPartsMustLength:] { @@ -91,6 +99,13 @@ func (h *Handler) ImportSheet(data *vars.DataForFulfill, df excel.DecodedFile) e return nil } +func parseI18nTextToExcelFieldKey(text string) string { + if v, ok := i18nMapByText[text]; ok { + return v + } + return text +} + func decodeMapToIssueSheetModel(data *vars.DataForFulfill, m map[IssueSheetColumnUUID]excel.Column) (_ []vars.IssueSheetModel, err error) { defer func() { if r := recover(); r != nil { @@ -116,9 +131,9 @@ func decodeMapToIssueSheetModel(data *vars.DataForFulfill, m map[IssueSheetColum groupType := parts[0] groupField := parts[1] switch groupType { - case "Common": + case fieldCommon: switch groupField { - case "ID": + case fieldID: if cell.Value == "" { // update model.Common.ID = 0 continue @@ -128,59 +143,57 @@ func decodeMapToIssueSheetModel(data *vars.DataForFulfill, m map[IssueSheetColum return nil, fmt.Errorf("invalid id: %s", cell.Value) } model.Common.ID = id - case "IterationName": + case fieldIterationName: model.Common.IterationName = cell.Value - case "IssueType": + case fieldIssueType: issueType, err := parseStringIssueType(cell.Value) if err != nil { return nil, err } model.Common.IssueType = issueType - case "IssueTitle": + case fieldIssueTitle: model.Common.IssueTitle = cell.Value - case "Content": + case fieldContent: model.Common.Content = cell.Value - case "State": + case fieldState: model.Common.State = cell.Value - case "Priority": + case fieldPriority: priority, err := parseStringPriority(cell.Value) if err != nil { return nil, err } model.Common.Priority = priority - case "Complexity": + case fieldComplexity: complexity, err := parseStringComplexity(cell.Value) if err != nil { return nil, err } model.Common.Complexity = complexity - case "Severity": + case fieldSeverity: severity, err := parseStringSeverity(cell.Value) if err != nil { return nil, err } model.Common.Severity = severity - case "CreatorName": + case fieldCreatorName: model.Common.CreatorName = cell.Value - case "AssigneeName": + case fieldAssigneeName: model.Common.AssigneeName = cell.Value - case "CreatedAt": + case fieldCreatedAt: model.Common.CreatedAt = vars.MustParseStringTime(cell.Value, groupField) - case "UpdatedAt": - model.Common.UpdatedAt = vars.MustParseStringTime(cell.Value, groupField) - case "PlanStartedAt": + case fieldPlanStartedAt: model.Common.PlanStartedAt = vars.MustParseStringTime(cell.Value, groupField) - case "PlanFinishedAt": + case fieldPlanFinishedAt: model.Common.PlanFinishedAt = vars.MustParseStringTime(cell.Value, groupField) - case "StartAt": + case fieldStartAt: model.Common.StartAt = vars.MustParseStringTime(cell.Value, groupField) - case "FinishAt": + case fieldFinishAt: model.Common.FinishAt = vars.MustParseStringTime(cell.Value, groupField) - case "EstimateTime": + case fieldEstimateTime: model.Common.EstimateTime = cell.Value - case "Labels": + case fieldLabels: model.Common.Labels = vars.ParseStringSliceByComma(cell.Value) - case "ConnectionIssueIDs": + case fieldConnectionIssueIDs: var ids []int64 for _, idStr := range vars.ParseStringSliceByComma(cell.Value) { id, err := parseStringIssueID(idStr) @@ -193,11 +206,12 @@ func decodeMapToIssueSheetModel(data *vars.DataForFulfill, m map[IssueSheetColum } model.Common.ConnectionIssueIDs = ids default: - return nil, fmt.Errorf("unknown common field: %s", groupField) + // just skip + continue } - case "RequirementOnly": + case fieldRequirementOnly: switch groupField { - case "InclusionIssueIDs": + case fieldInclusionIssueIDs: var ids []int64 for _, idStr := range vars.ParseStringSliceByComma(cell.Value) { id, err := parseStringIssueID(idStr) @@ -209,29 +223,29 @@ func decodeMapToIssueSheetModel(data *vars.DataForFulfill, m map[IssueSheetColum } } model.RequirementOnly.InclusionIssueIDs = ids - case "CustomFields": + case fieldCustomFields: model.RequirementOnly.CustomFields = append(model.RequirementOnly.CustomFields, vars.ExcelCustomField{ Title: parts[2], Value: cell.Value, }) } - case "TaskOnly": + case fieldTaskOnly: switch groupField { - case "TaskType": + case fieldTaskType: model.TaskOnly.TaskType = cell.Value - case "CustomFields": + case fieldCustomFields: model.TaskOnly.CustomFields = append(model.TaskOnly.CustomFields, vars.ExcelCustomField{ Title: parts[2], Value: cell.Value, }) } - case "BugOnly": + case fieldBugOnly: switch groupField { - case "OwnerName": + case fieldOwnerName: model.BugOnly.OwnerName = cell.Value - case "Source": + case fieldSource: model.BugOnly.Source = cell.Value - case "ReopenCount": + case fieldReopenCount: if cell.Value == "" { model.BugOnly.ReopenCount = 0 continue @@ -241,7 +255,7 @@ func decodeMapToIssueSheetModel(data *vars.DataForFulfill, m map[IssueSheetColum return nil, fmt.Errorf("invalid reopen count: %s", cell.Value) } model.BugOnly.ReopenCount = int32(reopenCount) - case "CustomFields": + case fieldCustomFields: model.BugOnly.CustomFields = append(model.BugOnly.CustomFields, vars.ExcelCustomField{ Title: parts[2], Value: cell.Value, @@ -354,7 +368,7 @@ func CreateOrUpdateIssues(data *vars.DataForFulfill, issueSheetModels []vars.Iss BaseModel: dbengine.BaseModel{ ID: uint64(model.Common.ID), CreatedAt: vars.ChangePointerTimeToTime(model.Common.CreatedAt), - UpdatedAt: vars.ChangePointerTimeToTime(model.Common.UpdatedAt), + UpdatedAt: vars.ChangePointerTimeToTime(model.Common.CreatedAt), }, PlanStartedAt: model.Common.PlanStartedAt, PlanFinishedAt: model.Common.PlanFinishedAt, diff --git a/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/import_old.go b/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/import_old.go index c3998fd8b67..f035f79679a 100644 --- a/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/import_old.go +++ b/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/import_old.go @@ -85,52 +85,52 @@ func convertOldIssueSheet(data *vars.DataForFulfill, sheet [][]string) ([]vars.I s := row[columnIdx] switch columnIdx { case 0: // ID - addM(m, NewIssueSheetColumnUUID("Common", "ID"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldID), s) case 1: // Title - addM(m, NewIssueSheetColumnUUID("Common", "IssueTitle"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldIssueTitle), s) case 2: // Content - addM(m, NewIssueSheetColumnUUID("Common", "Content"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldContent), s) case 3: // State - addM(m, NewIssueSheetColumnUUID("Common", "State"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldState), s) case 4: // Creator - addM(m, NewIssueSheetColumnUUID("Common", "CreatorName"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldCreatorName), s) case 5: // Assignee - addM(m, NewIssueSheetColumnUUID("Common", "AssigneeName"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldAssigneeName), s) case 6: // Owner - addM(m, NewIssueSheetColumnUUID("BugOnly", "OwnerName"), s) + addM(m, NewIssueSheetColumnUUID(fieldBugOnly, fieldOwnerName), s) case 7: // TaskType or BugSource switch issueType { case pb.IssueTypeEnum_TASK: - addM(m, NewIssueSheetColumnUUID("TaskOnly", "TaskType"), s) + addM(m, NewIssueSheetColumnUUID(fieldTaskOnly, fieldTaskType), s) case pb.IssueTypeEnum_BUG: - addM(m, NewIssueSheetColumnUUID("BugOnly", "Source"), s) + addM(m, NewIssueSheetColumnUUID(fieldBugOnly, fieldSource), s) } case 8: // Priority - addM(m, NewIssueSheetColumnUUID("Common", "Priority"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldPriority), s) case 9: // IterationName - addM(m, NewIssueSheetColumnUUID("Common", "IterationName"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldIterationName), s) case 10: // Complexity - addM(m, NewIssueSheetColumnUUID("Common", "Complexity"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldComplexity), s) case 11: // Severity - addM(m, NewIssueSheetColumnUUID("Common", "Severity"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldSeverity), s) case 12: // Labels - addM(m, NewIssueSheetColumnUUID("Common", "Labels"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldLabels), s) case 13: // IssueType - addM(m, NewIssueSheetColumnUUID("Common", "IssueType"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldIssueType), s) case 14: // PlanFinishedAt - addM(m, NewIssueSheetColumnUUID("Common", "PlanFinishedAt"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldPlanFinishedAt), s) case 15: // CreatedAt - addM(m, NewIssueSheetColumnUUID("Common", "CreatedAt"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldCreatedAt), s) case 16: // ConnectionIssueIDs - addM(m, NewIssueSheetColumnUUID("Common", "ConnectionIssueIDs"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldConnectionIssueIDs), s) case 17: // EstimateTime - addM(m, NewIssueSheetColumnUUID("Common", "EstimateTime"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldEstimateTime), s) case 18: // FinishedAt - addM(m, NewIssueSheetColumnUUID("Common", "FinishAt"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldFinishAt), s) case 19: // StartAt - addM(m, NewIssueSheetColumnUUID("Common", "PlanStartedAt"), s) + addM(m, NewIssueSheetColumnUUID(fieldCommon, fieldPlanStartedAt), s) case 20: // ReopenCount - addM(m, NewIssueSheetColumnUUID("BugOnly", "ReopenCount"), s) + addM(m, NewIssueSheetColumnUUID(fieldBugOnly, fieldReopenCount), s) default: } } @@ -139,11 +139,11 @@ func convertOldIssueSheet(data *vars.DataForFulfill, sheet [][]string) ([]vars.I s := row[i+oldExcelFormatCustomFieldRowColumnIndexFrom] switch propertyType { case pb.PropertyIssueTypeEnum_REQUIREMENT: - addM(m, NewIssueSheetColumnUUID("RequirementOnly", "CustomFields", customFieldNames[i]), s) + addM(m, NewIssueSheetColumnUUID(fieldRequirementOnly, fieldCustomFields, customFieldNames[i]), s) case pb.PropertyIssueTypeEnum_TASK: - addM(m, NewIssueSheetColumnUUID("TaskOnly", "CustomFields", customFieldNames[i]), s) + addM(m, NewIssueSheetColumnUUID(fieldTaskOnly, fieldCustomFields, customFieldNames[i]), s) case pb.PropertyIssueTypeEnum_BUG: - addM(m, NewIssueSheetColumnUUID("BugOnly", "CustomFields", customFieldNames[i]), s) + addM(m, NewIssueSheetColumnUUID(fieldBugOnly, fieldCustomFields, customFieldNames[i]), s) } } } diff --git a/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/import_vars.go b/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/import_vars.go index af04398aaee..e292d89a686 100644 --- a/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/import_vars.go +++ b/internal/apps/dop/providers/issue/core/query/issueexcel/sheets/sheet_issue/import_vars.go @@ -18,6 +18,7 @@ import ( "fmt" "strings" + "github.com/erda-project/erda/internal/apps/dop/providers/issue/core/query/issueexcel/vars" "github.com/erda-project/erda/pkg/excel" ) @@ -79,7 +80,7 @@ func (info *IssueSheetModelCellInfoByColumns) Add(uuid IssueSheetColumnUUID, cel info.M[uuid] = append(info.M[uuid], excel.Cell{Value: cellValue}) } -func (info *IssueSheetModelCellInfoByColumns) ConvertToExcelSheet() (excel.Rows, error) { +func (info *IssueSheetModelCellInfoByColumns) ConvertToExcelSheet(data *vars.DataForFulfill) (excel.Rows, error) { // create [][]excel.Cell var dataRowLength int // get data row length @@ -101,7 +102,11 @@ func (info *IssueSheetModelCellInfoByColumns) ConvertToExcelSheet() (excel.Rows, // set column title cells parts := uuid.Decode() for i, uuidPart := range parts { - rows[i][columnIndex] = excel.Cell{Value: uuidPart, Style: &excel.CellStyle{IsTitle: true}} + cellValue := data.I18n(uuidPart) + if parts[1] == fieldCustomFields && i == 2 { + cellValue = uuidPart + } + rows[i][columnIndex] = excel.Cell{Value: cellValue, Style: &excel.CellStyle{IsTitle: true}} } // auto merge title cells with same value autoMergeTitleCellsWithSameValue(rows[:uuidPartsMustLength]) diff --git a/internal/apps/dop/providers/issue/core/query/issueexcel/vars/data.go b/internal/apps/dop/providers/issue/core/query/issueexcel/vars/data.go index 13e818f4585..2ee8aea4522 100644 --- a/internal/apps/dop/providers/issue/core/query/issueexcel/vars/data.go +++ b/internal/apps/dop/providers/issue/core/query/issueexcel/vars/data.go @@ -17,6 +17,7 @@ package vars import ( "fmt" + "github.com/erda-project/erda-infra/providers/i18n" userpb "github.com/erda-project/erda-proto-go/core/user/pb" "github.com/erda-project/erda-proto-go/dop/issue/core/pb" "github.com/erda-project/erda/apistructs" @@ -27,7 +28,6 @@ import ( "github.com/erda-project/erda/internal/apps/dop/services/apierrors" legacydao "github.com/erda-project/erda/internal/core/legacy/dao" "github.com/erda-project/erda/pkg/excel" - "github.com/erda-project/erda/pkg/i18n" "github.com/erda-project/erda/pkg/strutil" ) @@ -40,7 +40,8 @@ type DataForFulfill struct { UserID string OrgID int64 ProjectID uint64 - Locale *i18n.LocaleResource + Tran i18n.Translator + Lang i18n.LanguageCodes StageMap map[query.IssueStage]string IterationMapByID map[int64]*dao.Iteration // key: iteration id IterationMapByName map[string]*dao.Iteration // key: iteration name diff --git a/internal/apps/dop/providers/issue/core/query/issueexcel/vars/export_type.go b/internal/apps/dop/providers/issue/core/query/issueexcel/vars/export_type.go new file mode 100644 index 00000000000..4e477e0d836 --- /dev/null +++ b/internal/apps/dop/providers/issue/core/query/issueexcel/vars/export_type.go @@ -0,0 +1,21 @@ +// Copyright (c) 2021 Terminus, Inc. +// +// Licensed 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 vars + +// 前端明确区分是`按筛选条件导出`还是`全量导出` +const ( + ExportTypeByFilter = "byFilter" + ExportTypeFull = "full" +) diff --git a/internal/apps/dop/providers/issue/core/query/issueexcel/vars/i18n.go b/internal/apps/dop/providers/issue/core/query/issueexcel/vars/i18n.go new file mode 100644 index 00000000000..550d390db5e --- /dev/null +++ b/internal/apps/dop/providers/issue/core/query/issueexcel/vars/i18n.go @@ -0,0 +1,43 @@ +// Copyright (c) 2021 Terminus, Inc. +// +// Licensed 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 vars + +import ( + "github.com/erda-project/erda-infra/providers/i18n" +) + +const ( + I18nLang_zh_CN = "zh-CN" + I18nLang_en_US = "en-US" +) + +func (data *DataForFulfill) I18n(key string, args ...interface{}) string { + if len(args) == 0 { + try := data.Tran.Text(data.Lang, key) + if try != key { + return try + } + } + return data.Tran.Sprintf(data.Lang, key, args...) +} + +func GetI18nLang(locale string) i18n.LanguageCodes { + lang, _ := i18n.ParseLanguageCode(locale) + if lang.Len() > 0 { + return lang + } + l, _ := i18n.ParseLanguageCode(I18nLang_zh_CN) + return l +} diff --git a/internal/apps/dop/providers/issue/core/query/issueexcel/vars/i18n_test.go b/internal/apps/dop/providers/issue/core/query/issueexcel/vars/i18n_test.go new file mode 100644 index 00000000000..c675f6c2a0b --- /dev/null +++ b/internal/apps/dop/providers/issue/core/query/issueexcel/vars/i18n_test.go @@ -0,0 +1,32 @@ +// Copyright (c) 2021 Terminus, Inc. +// +// Licensed 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 vars + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/erda-project/erda-infra/providers/i18n" +) + +func TestParseLangHeader(t *testing.T) { + lang, err := i18n.ParseLanguageCode(I18nLang_zh_CN) + assert.NoError(t, err) + for _, l := range lang { + assert.Equal(t, I18nLang_zh_CN, l.Code) + assert.Equal(t, float32(1), l.Quality) + } +} diff --git a/internal/apps/dop/providers/issue/core/query/issueexcel/vars/model.go b/internal/apps/dop/providers/issue/core/query/issueexcel/vars/model.go index bd26d7b2950..80332f8dc67 100644 --- a/internal/apps/dop/providers/issue/core/query/issueexcel/vars/model.go +++ b/internal/apps/dop/providers/issue/core/query/issueexcel/vars/model.go @@ -56,7 +56,6 @@ type ( CreatorName string AssigneeName string CreatedAt *time.Time - UpdatedAt *time.Time PlanStartedAt *time.Time PlanFinishedAt *time.Time StartAt *time.Time diff --git a/internal/apps/dop/providers/issue/core/service.go b/internal/apps/dop/providers/issue/core/service.go index bca3af6937d..97c4eeb6d09 100644 --- a/internal/apps/dop/providers/issue/core/service.go +++ b/internal/apps/dop/providers/issue/core/service.go @@ -27,6 +27,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/erda-project/erda-infra/base/logs" + "github.com/erda-project/erda-infra/providers/i18n" userpb "github.com/erda-project/erda-proto-go/core/user/pb" "github.com/erda-project/erda-proto-go/dop/issue/core/pb" "github.com/erda-project/erda/apistructs" @@ -56,6 +57,7 @@ type IssueService struct { ExportChannel chan uint64 ImportChannel chan uint64 identity userpb.UserServiceServer + translator i18n.I18n } func (i *IssueService) WithTestplan(testPlan *mttestplan.TestPlan) { @@ -71,6 +73,10 @@ func (i *IssueService) WithChannel(export, im chan uint64) { i.ImportChannel = im } +func (i *IssueService) WithTranslator(tran i18n.I18n) { + i.translator = tran +} + func (i *IssueService) DBClient() *dao.DBClient { return i.db }