- 新增 app.manifest 文件以实现系统 DPI 感知,确保界面在不同缩放设置下正常显示。 - 更新 MainForm.cs 和 Theme.cs,调整控件布局和样式以适应 DPI 变化。 - 修改 README.md,增加关于 DPI 感知的说明。 - 新增 MainFormUi.cs,负责界面布局与状态呈现,优化用户体验。 - 新增 debug.log 文件以记录错误信息,便于调试和问题追踪。
151 lines
9.9 KiB
Markdown
151 lines
9.9 KiB
Markdown
# TFSImport
|
||
|
||
TFS 2013 工作项批量导入工具集,用于把 Excel/CSV 里的工时台账(标题、状态、指派人、完成量、日期等)批量写成团队项目里的工作项,并挂到指定的父项(Product Backlog Item / 占位 Backlog Item)下,使其在「积压工作」页和任务板可见。
|
||
|
||
目标环境:
|
||
|
||
| 项 | 值 |
|
||
| --- | --- |
|
||
| 服务器 | `http://pdtfs01.dev.goldenstand.cn:8080/tfs/DefaultCollection` |
|
||
| 团队项目 | `Tools_Dev` |
|
||
| 团队 | `DataTechnologies` |
|
||
| 迭代 | `2026年Sprint\2026年Sprint10` |
|
||
| 认证 | 域账号 NTLM(内网直连,不走代理) |
|
||
|
||
## 两套工具,先用哪个
|
||
|
||
| | [TfsExcelImporter](TfsExcelImporter)(C# WinForms) | [tfs_import.py](tfs_import.py)(Python CLI) |
|
||
| --- | --- | --- |
|
||
| 通信协议 | TFS Client OM(VS/Team Explorer 同款内部 SOAP) | REST `/_apis/wit/*` |
|
||
| 适用 | **本环境可用**。该 TFS 2013 实例只有 `/_apis/projects` 通,`wit/fields`、`wit/classificationnodes`、`wit/wiql` 全部 404 | 服务器开放了 wit REST 路由时使用 |
|
||
| 数据源 | `.xlsx`(默认模板随程序发布) | `data/work_items.csv`(GBK/UTF-8 均可,需自备) |
|
||
| 界面 | 图形界面,预览后导入 | 命令行,`--dry-run` 先校验 |
|
||
|
||
Python 版在本环境会因 REST 路由 404 而失败,保留用于其它 TFS 实例或接口可用时的批量脚本化导入。
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
TFSImport/
|
||
├── tfs_import.py # Python 命令行导入器(REST)
|
||
├── config.json # Python 版配置
|
||
├── requirements.txt # requests / requests-ntlm
|
||
├── data/
|
||
│ └── work_items.example.csv # 列结构与示例行(UTF-8 BOM);真实台账不入库
|
||
├── templates/
|
||
│ └── TFS导入模板.xlsx # 默认模板:csproj 随构建拷到 exe 目录,「导出Excel模板」原样复制它
|
||
└── TfsExcelImporter/ # C# WinForms 桌面版(主用)
|
||
├── TfsExcelImporter.csproj # net48 + ClosedXML + TFS ExtendedClient 14.*
|
||
├── MainForm.cs # 导入 / 修 Area / 补挂父项流程
|
||
├── MainFormUi.cs # 界面外壳:步骤卡片布局、手风琴折叠、状态呈现
|
||
├── Theme.cs # 设计 token(颜色/字号/间距/缩放)与自绘控件
|
||
├── app.manifest # System DPI 感知,像素尺寸按缩放换算
|
||
├── TfsClient.cs # TFS Client OM 封装
|
||
├── ExcelService.cs # 读 Excel、导出默认模板、时间/数值归一
|
||
├── Config.cs # exe 同目录 config.json 读写
|
||
└── config.json # 桌面版配置
|
||
```
|
||
|
||
## 快速开始
|
||
|
||
### C# 桌面版(主用)
|
||
|
||
```powershell
|
||
dotnet build TfsExcelImporter.sln -c Release
|
||
# 或用 Visual Studio 打开 TfsExcelImporter.sln(目标框架 net48,无需装 VS 也能 dotnet build)
|
||
TfsExcelImporter\bin\Release\net48\TfsExcelImporter.exe
|
||
```
|
||
|
||
「导出Excel模板」复制的是 exe 同目录的 `TFS导入模板.xlsx`(构建时自动拷过去);单独分发 exe 时要带上这个文件,否则该按钮会报「未找到默认模板文件」。
|
||
|
||
界面按 4 步排:顶部状态条常驻显示服务器/项目/类型与连接状态,四张卡片一次只展开一张(展开一张会把其它收成一行摘要),预览表格永远占满剩余高度。
|
||
|
||
1. **步骤 1 连接**:填 URL、项目、工作项类型、认证方式与域账号,点「测试连接」——右上状态灯变绿即连通;内网保持「使用系统代理」**不勾选**。
|
||
2. **步骤 2 选数据**:点「加载迭代列表」选目标迭代(也可手填完整路径);点「导出 Excel 模板」拿默认模板,或「浏览...」选已有 `.xlsx`。需要挂父项时填「父 PBI Id」(默认全部挂同一个;Excel 里加 `PBI Id` 列可逐行指定不同父项)。
|
||
3. **步骤 3 预览核对**:选完文件自动读表,表格里逐行核对;缺迭代/文件路径无效时红字直接标在对应输入框下方,不再弹框。
|
||
4. **步骤 4 导入**:底部操作条点「开始导入 TFS」,进度条 + `n/m` + 成功/跳过/失败三个计数实时更新,日志抽屉逐行输出 `OK/SKIP/FAIL` 与工作项 ID;跑完点「打开报告 CSV」看逐行结果。
|
||
5. 历史数据 Area 不对、或漏挂父项时,展开底部「高级工具」用「按迭代修 Area」「补挂父项」批量纠正;Area 路径也在这一栏里填(以前只能手改 config.json)。
|
||
|
||
桌面版按 System DPI 感知实现,界面尺寸随 Windows 显示缩放换算;**改了显示缩放比例要重启程序**才生效。
|
||
|
||
### Python 版
|
||
|
||
```powershell
|
||
pip install -r requirements.txt
|
||
python tfs_import.py --dry-run # 只校验数据与字段映射,不写入
|
||
python tfs_import.py --list-fields # 列出服务器全部字段(显示名/引用名)
|
||
python tfs_import.py # 正式导入
|
||
python tfs_import.py --link-existing # 已导入的项补挂父链接(不新建)
|
||
python tfs_import.py --skip-existing-titles # 跳过服务器已有的同标题项
|
||
python tfs_import.py --input other.csv --limit 5
|
||
```
|
||
|
||
每次运行在 `data/import_report_<时间戳>.csv` 生成逐行结果报告(行号 / Title / 结果 / WorkItemId / 错误)。
|
||
|
||
## 配置说明
|
||
|
||
配置的语义、留空字段如何解析见 [tfs_import.py:49](tfs_import.py#L49) 与 [Config.cs](TfsExcelImporter/Config.cs),关键字段:
|
||
|
||
| 键 | 说明 |
|
||
| --- | --- |
|
||
| `tfs_url` / `project` / `work_item_type` | 目标集合、项目、工作项类型(`Task` 或 `Product Backlog Item`) |
|
||
| `iteration_path` | 可只写 `2026年Sprint\2026年Sprint10`,程序自动补项目名前缀 |
|
||
| `auth.mode` | `ntlm`(域账号,典型)/ `basic` / `default`(当前 Windows 凭据,仅桌面版) |
|
||
| `field_refs` | 语义名 → 字段引用名;自定义字段留空时按 `resolve_fields_by_display_name` 的显示名在服务器字段表里查(支持前缀模糊匹配,如 `Completed` → `Completed Work`) |
|
||
| `parent_title` / `parent_id` | 父工作项,二选一;按标题匹配不到会直接报错并提示改用 `parent_id` |
|
||
| `bypass_rules` | 是否请求绕过字段校验规则;桌面版走 Client OM(14.x 无 `SaveFlags.BypassRules`),实际由服务器规则决定 |
|
||
| `skip_existing_titles` | 同项目/同类型/同迭代下按标题去重 |
|
||
|
||
> 配置里含明文密码,请勿提交到共享仓库;`config.json` 保持空值,凭据本地填写。
|
||
|
||
## Excel / CSV 列
|
||
|
||
桌面版 Excel 模板 = 仓库里的 `templates\TFS导入模板.xlsx`,界面「导出Excel模板」直接复制它:
|
||
|
||
`Title`、`State`、`Assigned To`、`FinancialProject`、`Completed`、`LogDate`、`PBI Id`、`Description`
|
||
|
||
写入 TFS 的对应关系:
|
||
|
||
| 模板列 | 目标字段 |
|
||
| --- | --- |
|
||
| `Description` | `System.Description` |
|
||
| `Completed` | 显示名 `Completed`(模糊匹配到 `Completed Work`)解析出的字段 |
|
||
| —(无列) | `Original Estimate` 取该行 `Completed` 的值;引用名留空则按显示名 `Original Estimate` 解析,可写在 `OriginalEstimateRef` 里固定 |
|
||
|
||
系统日期列(`Created Date` / `Closed Date`)已从模板移除:`System.CreatedDate` 由服务器按导入时间填写,`Microsoft.VSTS.Common.ClosedDate` 不再写入、保持为空。
|
||
|
||
Python 版仍读 `data/work_items.example.csv` 的旧列(含 `CreatedDate`、`ClosedDate`、`Notes`),未随模板调整。
|
||
|
||
真实台账含人员与项目信息,不入库;本地准备数据文件:
|
||
|
||
```powershell
|
||
copy data\work_items.example.csv data\work_items.csv # Python 版默认读这个路径,已在 .gitignore 里
|
||
```
|
||
|
||
- 表头按名匹配,顺序不限,`AssignedTo` / `Assigned To` 等价;缺列按模板顺序兜底,但该列已被别的表头占用时不套用位置(旧版模板文件不会串列)。
|
||
- 空 `Title` 的行直接跳过;`State` 为空时用 `default_state`;未识别的列(如 `Group`)忽略。
|
||
- 日期支持 `yyyy-MM-dd[ / ][ HH:mm[:ss]]` 等格式,Excel 日期序列号(如 `46300`)会被还原成日期。
|
||
- CSV 从 Excel 另存时注意编码,脚本按 `utf-8-sig` → `gbk` 依次尝试。
|
||
|
||
## 环境坑点
|
||
|
||
这些是实测结论,改代码前先看:
|
||
|
||
- **wit REST 路由全 404**:TFS 2013 RTM/低 Update 不带 `/_apis/wit/*`,`Classification.asmx?WSDL` 也被禁 → 桌面版整体走 Client OM(NuGet `Microsoft.TeamFoundationServer.ExtendedClient` 钉 `14.*`,对应 VS2015 客户端代际;15.x 不兼容 TFS 2013)。
|
||
- **502 Bad Gateway**:TFS OM 内部用 `HttpWebRequest`,默认跟随 IE 系统代理,代理会拦内网 TFS。桌面版默认把 `WebRequest.DefaultWebProxy` 置空直连;确实要走代理时勾「使用系统代理」并带上 Windows 凭据过代理认证。
|
||
- **TF400276 / 关闭状态只读**:改已关闭工作项的 Area 会被拒;`FixAreaAsync` 走「重新打开 → 改 Area → 还原状态」的状态循环,中途失败回滚状态。
|
||
- **`System.CreatedDate` 写不进去**:OM 14.x 没有 `SaveFlags.BypassRules`(15.x 才加),该字段服务器托管;`Microsoft.VSTS.Common.ClosedDate` 在 `State=Done` 时按普通字段可尝试写入。模板已去掉日期列,所以导入不再走这条路——若某模板要求 `Done` 必填 Closed Date,需另加列或改回写日期。
|
||
- **孤立 Task 在积压工作页不可见**:TFS 2013 的 backlog/任务板按「父项 → 子任务」组织,没挂父链接的 Task 只能在查询里看到,所以导入必须带 `parent_title`/`parent_id`。
|
||
- **DNS 解析失败 / 超时**:多为 VPN 或网络断开,不是代码问题;桌面版会对这类异常附加排查提示。
|
||
|
||
## 验证
|
||
|
||
改动 Python 版后至少跑:
|
||
|
||
```powershell
|
||
python -m py_compile tfs_import.py
|
||
python tfs_import.py --dry-run
|
||
```
|
||
|
||
桌面版改完跑一次 `--limit` 级别的小批量导入(先删服务器已有同标题项或开启跳过),并在积压工作页展开父项确认子任务可见。
|