import React, { useReducer } from 'react'
import omitNil from 'omit-nil'
import { robust } from 'memoize-fn'
import {
Button,
Dropdown,
Menu,
Input,
Modal,
Drawer,
Typography,
ModalProps,
Form,
Row,
Col,
Avatar,
Select,
notification,
Spin
} from 'antd'
import p from 'prefix-classname'
import isHotKey from 'is-hotkey'
import 'github-markdown-css/github-markdown.css'
const cn = p()
const c = p('jira_modal-import')
const labelCol = { style: { width: 100 } }
import './style.sass'
import JiraApiBrowser from '../../shared/jira-api-browser'
import parseHtmlTreeNode from '../../shared/parse-html-treenode'
import treeNodeToMdast from '../../shared/tree-node-to-mdast'
import JiraImportPreview from '../import-preview'
import mdastToTreeNodes from '../../shared/mdast-to-tree-node'
import { markdown2confluence } from '../../shared/markdown-to-confluence'
import mdastStringify from '../../shared/mdast-stringify'
import { JiraSuggest } from '../epic-link-suggest'
import TreeNode from '../../shared/tree-node'
import url from 'url'
import treeNodesToConfluence from '../../shared/tree-node-to-confluence'
import UserSuggest from '../user-suggest'
const IssueLabel = (x) => (
{x.name}
)
const JiraModalImport = React.forwardRef<
{},
{
token: string
} & ModalProps
>(({ token, ...props }, ref) => {
React.useImperativeHandle(ref, () => ({}), [])
const importPreviewRef = React.useRef(null)
const [input, setInput] = React.useState('')
const [issueTypes, setIssueTypes] = React.useState([])
const [loading, setLoading] = React.useState(false)
const onImportInputKeydown = React.useCallback((evt) => {
const isCollapsed = evt.target.selectionEnd === evt.target.selectionStart
if (isCollapsed && isHotKey('tab', evt)) {
document.execCommand('insertText', false, ' ')
evt.preventDefault()
}
}, [])
const jiraApi = React.useMemo(
() =>
new JiraApiBrowser({
password: token
}),
[token]
)
const [fields, setFields] = React.useState>([])
const queryFields = React.useCallback(() => {
return jiraApi.queryFields()
}, [jiraApi, JIRA.API.Projects.getCurrentProjectKey()])
React.useEffect(() => {
if (props.visible && JIRA.API.Projects.getCurrentProjectKey()) {
setLoading('加载 Issue 配置...')
queryFields()
.then((fields) => {
setFields(fields)
})
.finally(() => {
setLoading(false)
})
} else {
setFields([])
}
}, [props.visible, queryFields, setFields])
const [form] = Form.useForm()
const [parseOptions, setParseOptions] = React.useState({
estimateRegExp: `\\s+\\(?(\\d+)'?\\s*$` // `(?(\\d+)'?\\s*$`
})
const [sprintData, setSprintData] = React.useState({})
const [jiraComponents, setJiraComponents] = React.useState([])
const sprintFetcher = React.useCallback(async (val) => {
const renderOption = (d: any) => {
return {
value: d.id,
label: (
{d.name}
({d.stateKey})
)
}
}
const res = await jiraApi.querySuggestSprints({
query: val
})
const { suggestions, allMatches } = res.data || {}
return {
建议: suggestions.map(renderOption),
全部: allMatches.map(renderOption)
}
}, [])
React.useEffect(() => {
const fetch = async () => {
if (JIRA?.API?.Projects?.getCurrentProjectKey()) {
setLoading(true)
const res = await jiraApi.queryProject(JIRA?.API?.Projects?.getCurrentProjectKey())
if (res.data?.issueTypes) {
setIssueTypes(res.data?.issueTypes)
}
const components = await jiraApi.queryComponents().catch(() => [])
setJiraComponents(components)
setLoading(false)
}
}
fetch()
}, [setLoading, JIRA?.API?.Projects?.getCurrentProjectKey()])
const parseOpts = React.useMemo(() => {
let estimateRegExp
try {
estimateRegExp = new RegExp(parseOptions.estimateRegExp)
} catch (e) {
console.error(e)
}
return {
...parseOptions,
estimateRegExp
}
}, [parseOptions])
const hasKey = React.useCallback(
(key) => {
return fields.find((f) => f.id === key)
},
[fields]
)
return (
{
const mdast = importPreviewRef.current?.mdast
const nodes = mdastToTreeNodes(mdast)
const getReqBody = (nodes: TreeNode[], type: 'issue' | 'subTask') => {
const { issuePrefix, ...rawBody } = form.getFieldsValue() || {}
const commonDescription = markdown2confluence(rawBody.description || '')
return {
...rawBody,
components: rawBody.components?.map((id) => ({ id })),
tasksBody: nodes.map((node) => {
const selfDescription = treeNodesToConfluence(node.children)
return {
...node.data.params,
summary: (
(type === 'issue'
? [node.value && issuePrefix ? issuePrefix : null, node.value].filter(Boolean).join('')
: node.value) || ''
).replace(/\n/g, ' '),
description: [
commonDescription,
selfDescription && commonDescription && '\n======== 子任务如下 =========\n',
selfDescription
]
.filter(Boolean)
.join('\n')
}
})
}
}
setLoading(true)
const parentRes = await jiraApi.createIssues(getReqBody(nodes, 'issue'), { toastSuccess: false })
if (parentRes.data.issues && parentRes.data.issues.length) {
const subTasks = []
parentRes.data.issues.forEach((issue, i) => {
subTasks.push(
...nodes[i].children.map((node) => {
node.data.params = {
...node.data.params,
parent: {
key: issue.key
},
issuetype: '5',
// 子任务不能存在以下属性
dod: undefined,
sprint: undefined,
epicLink: undefined
}
return node
})
)
})
let issues = parentRes.data.issues
if (issues.length === nodes.length) {
const updateIssuesPromiseList = issues
.map((issue) => issue.id)
.map(async (id, i) => {
if (nodes[i]?.data?.estimate) {
return jiraApi.updateIssue(id, { estimate: nodes[i]?.data?.estimate }, { toast: false })
}
return
})
Promise.all(updateIssuesPromiseList).catch(console.error)
}
if (subTasks.length) {
const subTasksRes = await jiraApi.createIssues(getReqBody(subTasks, 'subTask'), { toastSuccess: false })
issues = parentRes.data.issues.concat(subTasksRes.data.issues || [])
}
const string = issues.map((x) => x.key).join(',')
notification.success({
duration: 0,
message: '创建 Jira Issue 成功',
description: (
{string}
)
})
}
setLoading(false)
}}
width={1200}
>
公共配置
{!!fields.length && (
)}
Issue 录入
格式说明
{
setInput(event.target.value)
}}
placeholder={'输入 markdown 或者复制 石墨文档内容至此'}
className={c('__import-left')}
onPaste={(evt) => {
const html = evt.clipboardData.getData('text/html')
console.log('html', html)
if (html) {
const treeNode = parseHtmlTreeNode(html)
// console.log('treeNode', treeNode);
if (treeNode !== false) {
const mdast = treeNodeToMdast(treeNode)
const mdText = mdastStringify(mdast)
console.log({ mdast, mdText, treeNode })
evt.preventDefault()
document.execCommand('insertText', false, mdText)
}
}
}}
cols={12}
/>
)
})
export default JiraModalImport