Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions .github/ISSUE_TEMPLATE/sub_issue_technical.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: 🔧 Feature(开发用)
description: 基于用户 Issue 拆解的技术实现方案(开发人员使用)
title: "[Feature] "
labels: ["feature"]
body:
- type: markdown
attributes:
value: |
此模板供开发人员使用,用于将用户提交的 Issue 转换为具体的技术实现方案。

- type: input
id: parent_issue
attributes:
label: 关联 Issue
description: 对应的 nex-taas-feedback 用户 Issue(格式:china-qijizhifeng/nex-taas-feedback#123)
placeholder: "china-qijizhifeng/nex-taas-feedback#123"
validations:
required: true

- type: textarea
id: technical_design
attributes:
label: 技术方案
description: 完整的技术实现方案
placeholder: |
## 背景分析
- 当前系统现状:...
- 存在的问题/差距:...
- 可复用的现有能力:...

## 竞品分析
| 竞品/方案 | 实现方式 | 优点 | 缺点 | 适用场景 |
|-----------|----------|------|------|----------|
| 方案 A | | | | |
| 方案 B | | | | |

选型建议:...

## 整体架构
(附架构图或 Mermaid 流程图)

## 核心组件及职责
- 组件 A:负责...
- 组件 B:负责...

## 关键接口定义
```python
class XXX:
def method(self, ...) -> ...:
"""..."""
```

## 数据流与调用关系
数据输入 -> 处理 A -> 处理 B -> 输出

## 文件变更清单
- **新建** `path/to/new_file.py` — 描述
- **修改** `path/to/existing.py` — 修改内容

## API / 配置变更
- 新增 API: ...
- 配置变更: ...

## 依赖与兼容性
- 新增依赖:...
- 向后兼容性:...

## 风险评估
| 风险 | 影响 | 概率 | 应对措施 |
|------|------|------|----------|

## 里程碑与排期
- [ ] 阶段 1:xxx(预计 x 天)
- [ ] 阶段 2:xxx(预计 x 天)
validations:
required: true

- type: textarea
id: test_plan
attributes:
label: 测试方案
description: 完整的验证与测试计划
placeholder: |
## 单元测试
- 测试用例 1:...
- 测试用例 2:...

## 集成测试
- 端到端测试场景:...
- 测试数据准备:...

## 性能验证
- 基准指标:...
- 预期表现:...
- 压测方案:...

## 验收标准
- [ ] 标准 1
- [ ] 标准 2

## 回归测试
- 影响范围:...
- 需要回归的模块:...
validations:
required: true
88 changes: 88 additions & 0 deletions .github/workflows/auto-link-parent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Auto-link as Sub-issue

on:
issues:
types: [opened]

jobs:
link-parent:
runs-on: ubuntu-latest
steps:
- name: Link as sub-issue to parent
uses: actions/github-script@v7
with:
github-token: ${{ secrets.PROJECT_TOKEN }}
script: |
const body = context.payload.issue.body || '';

// Parse "关联 Issue" field - matches formats like:
// china-qijizhifeng/nex-taas-feedback#123
// https://github.com/china-qijizhifeng/nex-taas-feedback/issues/123
let parentOwner, parentRepo, parentNumber;

// Try URL format first
const urlMatch = body.match(/https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)/);
if (urlMatch) {
[, parentOwner, parentRepo, parentNumber] = urlMatch;
} else {
// Try org/repo#number format
const refMatch = body.match(/([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)#(\d+)/);
if (refMatch) {
[, parentOwner, parentRepo, parentNumber] = refMatch;
}
}

if (!parentNumber) {
console.log('No parent issue reference found, skipping');
return;
}

parentNumber = parseInt(parentNumber);
console.log(`Found parent: ${parentOwner}/${parentRepo}#${parentNumber}`);

// Get parent issue node ID
const parentResult = await github.graphql(`
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
issue(number: $number) {
id
}
}
}
`, { owner: parentOwner, repo: parentRepo, number: parentNumber });

const parentId = parentResult.repository.issue.id;

// Get current issue node ID
const childResult = await github.graphql(`
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
issue(number: $number) {
id
}
}
}
`, {
owner: context.repo.owner,
repo: context.repo.repo,
number: context.issue.number
});

const childId = childResult.repository.issue.id;

// Add as sub-issue
try {
await github.graphql(`
mutation($parentId: ID!, $childId: ID!) {
addSubIssue(input: { issueId: $parentId, subIssueId: $childId }) {
issue {
id
}
}
}
`, { parentId, childId });

console.log(`✅ Linked ${context.repo.owner}/${context.repo.repo}#${context.issue.number} as sub-issue of ${parentOwner}/${parentRepo}#${parentNumber}`);
} catch (e) {
console.log(`❌ Failed to link: ${e.message}`);
}
142 changes: 142 additions & 0 deletions .github/workflows/feishu-notify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: Notify Feishu on New Issue

on:
issues:
types: [opened]

jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Send Feishu Card Notification
uses: actions/github-script@v7
env:
FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }}
with:
script: |
const issue = context.payload.issue;
const author = issue.user.login;
const title = issue.title;
const url = issue.html_url;
const body = issue.body || '';
const labels = issue.labels.map(l => l.name);

// Determine if bug or feature based on issue type or title prefix
const isBug = title.startsWith('[Bug]') || labels.includes('bug');
const isFeature = title.startsWith('[FR]') || labels.includes('enhancement');

let cardHeader, cardColor, cardType, cardContent;

if (isBug) {
cardColor = 'red';
cardHeader = '😣问题反馈';

// Parse bug report fields from body
let description = '';
const descMatch = body.match(/### 问题描述\s*\n\s*\n([\s\S]*?)(?=\n###|$)/);
if (descMatch) description = descMatch[1].trim();

cardContent = [
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": `问题反馈:\n反馈人:**${author}**\n**问题概述:${title.replace('[Bug] ', '')}**\n${description ? description.substring(0, 500) : ''}\n\n请及时查看并处理该问题。`
}
},
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": {
"tag": "plain_text",
"content": "问题详情🔍"
},
"url": url,
"type": "default"
}
]
},
{ "tag": "hr" },
{
"tag": "note",
"elements": [
{
"tag": "lark_md",
"content": "来自 [TaaS迭代](https://github.com/orgs/china-qijizhifeng/projects/3)"
}
]
}
];
} else {
cardColor = 'green';
cardHeader = '⏰新增需求';

let description = '';
const descMatch = body.match(/### 功能描述\s*\n\s*\n([\s\S]*?)(?=\n###|$)/);
if (descMatch) description = descMatch[1].trim();

cardContent = [
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": `新增需求:\n提需人:**${author}**\n**需求概述:${title.replace('[FR] ', '')}**\n${description ? description.substring(0, 500) : ''}\n\n请及时查看并处理该需求,如有疑问可与提需人沟通。`
}
},
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": {
"tag": "plain_text",
"content": "需求详情🔍"
},
"url": url,
"type": "default"
}
]
},
{ "tag": "hr" },
{
"tag": "note",
"elements": [
{
"tag": "lark_md",
"content": "来自 [TaaS迭代](https://github.com/orgs/china-qijizhifeng/projects/3)"
}
]
}
];
}

const card = {
"msg_type": "interactive",
"card": {
"header": {
"title": {
"tag": "plain_text",
"content": cardHeader
},
"template": cardColor
},
"elements": cardContent
}
};

const webhookUrl = process.env.FEISHU_WEBHOOK_URL;
if (!webhookUrl) {
console.log('FEISHU_WEBHOOK_URL not set, skipping notification');
return;
}

const response = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(card)
});

const result = await response.json();
console.log('Feishu response:', JSON.stringify(result));