import React, { Component } from 'react';
import { observer } from 'mobx-react';
import { toJS } from 'mobx';
import IssueStore from '../../../stores/project/sprintFollow/IssueStore/IssueStore';
import {
Page, Header, Content, stores, axios,
} from '@choerodon/boot';
import {
Button, Icon, Select, DatePicker,
Modal, Input, Form, Tooltip, Table
} from 'choerodon-ui';
import _ from 'lodash';
import './Issue/Issue.scss';
import moment from 'moment';
import { multiple } from '../../../assets/image';
import {getParams, delta2Html} from "../../../common/utils";
import StatusTag from '../../../components/StatusTag/StatusTag';
import TestStatusTags from '../../../components/TestStatusTags/StatusTags';
import DemandStatusTag from '../../../components/DemandStatusTag';
const { AppState } = stores;
const { Option } = Select;
const { RangePicker } = DatePicker;
const FormItem = Form.Item;
const getIssueStatus = (record) => {
return (
<Tooltip mouseEnterDelay={0.5} title={`问题状态: ${record.statusMapDTO && record.statusMapDTO.name}`}>
<StatusTag
data={record.issueInfosDTO ? record.issueInfosDTO.statusMapDTO : record.statusMapDTO}
style={{ display: 'inline-block', verticalAlign: 'middle' }}
/>
</Tooltip>
);
}
const getExecuteStatus = (status) => {
return (
<Tooltip mouseEnterDelay={0.5} >
<TestStatusTags
name={status.statusName}
color={status.statusColor}
/>
</Tooltip>
);
}
const DemandStatusName = (record) => {
return (
<DemandStatusTag
record={record}
style={{ display: 'inline-block', verticalAlign: 'middle' }}
/>
);
}
const getName = (issue, type) => {
let dom = null;
if (issue.summary) {
dom = issue.summary;
} else if (issue.cycleName) {
dom = issue.cycleName + "/" + issue.folderName;
} else if (issue.issueInfosDTO) {
dom = issue.issueInfosDTO.summary;
}
const urlParams = AppState.currentMenuType;
let jumpUrl = '';
return (
<Tooltip mouseEnterDelay={0.5} placement="topLeft" title={`${dom}`}>
<span
className="c7n-Issue-summary"
onClick={async () => {
if (type === 'agile' || issue.typeCode === 'bug') {
let res = await axios.get(`/agile/v1/projects/${urlParams.id}/issues/${issue.issueId}?organizationId=${urlParams.organizationId}`)
jumpUrl = `/#/agile/issue?type=project&id=${urlParams.id}&name=${urlParams.name}&organizationId=${urlParams.organizationId}¶mName=${res.issueNum}¶mIssueId=${res.issueId}¶mOpenIssueId=${res.issueId}¶mUrl=/agile/issue-follow`
} else if (type === 'apply') {
let res = await axios.get(`/agile/v1/projects/${urlParams.id}/idp/issues/${issue.issueId}?organizationId=${urlParams.organizationId}`)
jumpUrl = `/#/agile/demand-application?type=project&id=${urlParams.id}&name=${urlParams.name}&organizationId=${urlParams.organizationId}¶mName=${res.issueNum}¶mIssueId=${res.issueId}¶mOpenIssueId=${res.issueId}¶mUrl=/agile/issue-follow`
} else if (type === 'test') {
jumpUrl = `/#/testManager/IssueManage/testCase/${issue.issueId}?type=project&id=${urlParams.id}&name=${urlParams.name}&organizationId=${urlParams.organizationId}`
} else if (type === 'execute') {
jumpUrl = `/#/testManager/TestExecute/execute/${issue.executeId}?type=project&id=${urlParams.id}&name=${urlParams.name}&organizationId=${urlParams.organizationId}&cycleId=${issue.cycleId}`
}
window.open(jumpUrl);
}}
>
{dom}
</span>
</Tooltip>
)
}
// 自定义列宽度
const customColumn = {
topWidth: '16%',
oneWidth: '20%',
twoWidth: '25%',
threeWidth: '40%'
};
// 获取某一层级的全部要节点
const flattenItem = (staticIssue, staticIndex, type) => {
let allArr = [];
let handleIndex = 1;
const handle = (issue, index) => {
if (staticIndex === index) {
allArr.push(issue)
return false
}
let tempIndex = index + 1;
if (staticIndex > index) {
issue.child && issue.child.map((item) => {
handle(item, tempIndex);
})
}
}
handle(JSON.parse(JSON.stringify(staticIssue)), handleIndex);
if (type === 'execute') {
return arrayUnique2(allArr, 'cycleId');
} else if (type === 'currentExecute') {
return arrayUnique2(allArr, 'folderName');
} else {
return arrayUnique2(allArr, 'issueId');
}
}
const getArrow = (show, issue) => {
if (issue.typeCode && issue.typeCode === 'bug') { return null; }
return !show ? <Icon style={{marginBottom: '10px'}} type="navigate_next" /> : <Icon style={{marginBottom: '10px'}} type="expand_more" />
}
// 返回未展开问题的各种状态
const arrToGroup = (arr, type) => {
var map = {}, dest = [];
// 过滤掉缺陷
let tempArr = [];
if (type === 'execute') {
JSON.parse(JSON.stringify(arr)).map((item) => {
if (item.typeCode === 'bug') {
return;
} else {
tempArr.push({
...item,
...item.issueInfosDTO.statusMapDTO
})
}
})
} else {
JSON.parse(JSON.stringify(arr)).map((item) => {
if (item.typeCode === 'bug') {
return;
} else {
tempArr.push({
...item,
...item.statusMapDTO,
})
}
})
}
for(var i = 0; i < tempArr.length; i++){
var ai = tempArr[i];
if(!map[ai.name]){
dest.push({
name: ai.name,
item: [ai]
});
map[ai.name] = ai;
}else{
for(var j = 0; j < dest.length; j++){
var dj = dest[j];
if(dj.name == ai.name){
dj.item.push(ai);
break;
}
}
}
}
let str = '';
dest.map((item) => {
str = str + `${item.name}: ${item.item.length};`
})
return (
<Tooltip mouseEnterDelay={0.5} placement="topLeft" title={`${str}`}>
<span className="c7n-Issue-summary">
{str}
</span>
</Tooltip>
);
}
// 返回未展开问题的各种状态
const arrToGroupExecute = (arr, executeList) => {
var map = {}, dest = [];
// 过滤掉缺陷
let tempArr = [];
JSON.parse(JSON.stringify(arr)).map((item) => {
let status = _.find(executeList, { statusId: item.executionStatus })
tempArr.push({
...item,
...status,
})
})
for(var i = 0; i < tempArr.length; i++){
var ai = tempArr[i];
if(!map[ai.statusName]){
dest.push({
statusName: ai.statusName,
item: [ai]
});
map[ai.statusName] = ai;
}else{
for(var j = 0; j < dest.length; j++){
var dj = dest[j];
if(dj.statusName == ai.statusName){
dj.item.push(ai);
break;
}
}
}
}
let str = '';
dest.map((item) => {
str = str + `${item.statusName}: ${item.item.length};`
})
return (
<Tooltip mouseEnterDelay={0.5} placement="topLeft" title={`${str}`}>
<span className="c7n-Issue-summary">
{str}
</span>
</Tooltip>
);
}
const arrToGroupInBug = (arr) => {
var map = {}, dest = [];
// 过滤掉缺陷
let tempArr = [];
JSON.parse(JSON.stringify(arr)).map((item) => {
if (item.issueInfosDTO) {
tempArr.push({
...item,
...item.issueInfosDTO.statusMapDTO
})
} else {
tempArr.push({
...item,
...item.statusMapDTO,
})
}
})
for(var i = 0; i < tempArr.length; i++){
var ai = tempArr[i];
if(!map[ai.name]){
dest.push({
name: ai.name,
item: [ai]
});
map[ai.name] = ai;
}else{
for(var j = 0; j < dest.length; j++){
var dj = dest[j];
if(dj.name == ai.name){
dj.item.push(ai);
break;
}
}
}
}
let str = '';
dest.map((item) => {
str = str + `${item.name}: ${item.item.length};`
})
return (
<Tooltip mouseEnterDelay={0.5} placement="topLeft" title={`${str}`}>
<span className="c7n-Issue-summary">
{str}
</span>
</Tooltip>
)
}
// 根据数组内的对象的某个参数去重
const arrayUnique2 = (arr, name) => {
var hash = {};
return arr.reduce(function (item, next) {
hash[next[name]] ? '' : hash[next[name]] = true && item.push(next);
return item;
}, []);
}
// 获取一个问题的所有的bugs并且去重
const getAllBugs = (staticIssues) => {
let arr = [];
const getBugs = (issues) => {
for (let i in issues) {
if (issues[i].typeCode === 'bug' || issues[i].defectType) {
arr.push(issues[i]);
}
if (issues[i].child) {
getBugs(issues[i].child);
}
}
}
getBugs(JSON.parse(JSON.stringify(staticIssues.child)));
arr = arrayUnique2(arr, 'issueId')
return arr;
}
const getStatus = (issue, show, classCount, executeList) => {
let renderDom = null;
const styleCount = {
color: 'black', width: 198,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}
const isNotFirst = {
marginLeft: 50
}
const styleFirst = {
marginLeft: 18
}
const bugPL = {
marginLeft: 70
}
if (issue.typeCode && issue.typeCode === 'bug') { return null; }
if (!issue.child || !issue.child.length) {
return <div style={{ marginLeft: '18px', padding: '4px 0' }}>-</div>;
}
let bugList = getAllBugs(_.cloneDeep(issue));
if (!show) {
switch (classCount) {
case 'one': {
let twoList = flattenItem(_.cloneDeep(issue), 2);
// 有测试执行就有测试用例
let threeList = flattenItem(_.cloneDeep(issue), 3).filter((item) => (item.testExecuteTrackDTOS));
// 有 cycleId 就证明是测试执行
let fourList = flattenItem(_.cloneDeep(issue), 4, 'execute').filter((item) => (item.cycleId));
renderDom = (
<React.Fragment>
<div style={{ ...styleCount }}>
<div>总共:{twoList.length}</div>
<div>{arrToGroup(twoList)}</div>
</div>
<div style={{ ...styleCount, ...isNotFirst }}>
<div>总共:{threeList.length}</div>
<div>{arrToGroup(threeList)}</div>
</div>
<div style={{ ...styleCount, ...isNotFirst }}>
<div>总共:{fourList.length}</div>
<div>{arrToGroupExecute(fourList, executeList)}</div>
</div>
<div style={{ ...styleCount, ...isNotFirst, ...bugPL }}>
<div>总共:{bugList.length}</div>
<div>{arrToGroupInBug(bugList)}</div>
</div>
</React.Fragment>
);
break;
};
case 'tow': {
// 有测试执行就有测试用例
let threeList = flattenItem(_.cloneDeep(issue), 2).filter((item) => (item.testExecuteTrackDTOS));
// 有 cycleId 就证明是测试执行
let fourList = flattenItem(_.cloneDeep(issue), 3, 'currentExecute').filter((item) => (item.cycleId));
renderDom = (
<React.Fragment>
<div style={{ ...styleCount }}>
<div>总共:{threeList.length}</div>
<div>{arrToGroup(threeList)}</div>
</div>
<div style={{ ...styleCount, ...isNotFirst }}>
<div>总共:{fourList.length}</div>
<div>{arrToGroupExecute(fourList, executeList)}</div>
</div>
<div style={{ ...styleCount, ...isNotFirst, ...bugPL }}>
<div>总共:{bugList.length}</div>
<div>{arrToGroupInBug(bugList)}</div>
</div>
</React.Fragment>
);
break;
};
case 'three': {
// 有 cycleId 就证明是测试执行
let fourList = flattenItem(_.cloneDeep(issue), 2, 'currentExecute').filter((item) => (item.cycleId));
renderDom = (
<React.Fragment>
<div style={{ ...styleCount }}>
<div>总共:{fourList.length}</div>
<div>{arrToGroupExecute(fourList, executeList)}</div>
</div>
<div style={{ ...styleCount, ...isNotFirst, ...bugPL }}>
<div>总共:{bugList.length}</div>
<div>{arrToGroupInBug(bugList)}</div>
</div>
</React.Fragment>
);
break;
};
case 'four': {
renderDom = (
<div style={{ ...styleCount }}>
<div>总共:{bugList.length}</div>
<div>{arrToGroupInBug(bugList)}</div>
</div>
);
break;
};
}
return <div style={{ display: 'flex', marginTop: '5px', marginLeft: 18 }}>{renderDom}</div>
} else {
return null;
}
}
let exStatus = [];
let onTestStatus = [];
@observer
class Issue extends Component {
constructor(props) {
super(props);
this.state = {
// 实现原理,利用第二层开始id不可能一样的问题
dataList: [],
showList: [],
execute: [],
currentIssue: {
summary: ''
}
}
this.onlyKey = 0;
}
componentDidMount = async () => {
const urlParams = AppState.currentMenuType;
let dataList = await this.reload();
let copyData = this.setShow(dataList);
let tempShowList = [];
let execute = await axios.post(`/test/v1/projects/${urlParams.id}/status/query`, {
projectId: urlParams.id,
statusType: "CYCLE_CASE"
});
let testStatus = await axios.get(`/issue/v1/projects/${urlParams.id}/schemes/query_status_by_project_id?apply_type=test&organizationId=${urlParams.organizationId}`);
exStatus = execute;
onTestStatus = testStatus;
dataList.map(() => {
tempShowList.push(false)
});
this.setState({
dataList: copyData,
showList: tempShowList,
execute: execute
})
}
reload = async () => {
const {issueId} = getParams();
const urlParams = AppState.currentMenuType;
let issue = await this.getCurrentIssue();
let treeData = await axios.get(`/test/v1/projects/${urlParams.id}/issueTrack/${issueId}?organizationId=${urlParams.organizationId}`);
let res = await this.handleDataToTree(issue, treeData);
return res;
}
// 判断是点击故事缺陷进来 还是 点击需求跟踪
getCurrentIssue = async () => {
const urlParams = AppState.currentMenuType;
const {issueId} = getParams();
let issue = await axios.get(`agile/v1/projects/${urlParams.id}/issues/${issueId}?organizationId=${urlParams.organizationId}`);
this.setState({ currentIssue: issue });
return issue;
}
// 构造我所需要的树形数据
handleDataToTree = async (issue, treeData) => {
if (issue.typeCode === 'issue_apply') {
// 需求跟踪
return await this.handleApply(treeData);
} else {
// 故事或者缺陷
let res = await this.handleStoryOrBugs(treeData)
return res;
}
}
handleApply = async (res) => {
let data = {};
let followData = {};
// 如果需求申请存在就存起来
if (res.issueApplyTrackDTOList && res.issueApplyTrackDTOList.length) {
// data是顶层的需求跟踪
data.applyList = res.issueApplyTrackDTOList;
data.summary = res.issueApplyTrackDTOList[0].summary;
data.issueId = res.issueApplyTrackDTOList[0].issueId;
// 需求跟踪是顶层节点,下面就是需求了,直接把需求绑定到顶层的child
data.child = res.issueTrackDTOList;
}
let resd = await this.handleTreeData([data]);
return resd;
}
handleStoryOrBugs = async (res) => {
// 虚拟顶层节点
let data = {};
let followData = {};
// 如果需求申请存在就存起来
if (res.issueApplyTrackDTOList && res.issueApplyTrackDTOList.length) {
// data是顶层的需求跟踪
data.applyList = res.issueApplyTrackDTOList;
data.summary = res.issueApplyTrackDTOList[0].summary;
data.issueId = res.issueApplyTrackDTOList[0].issueId;
// 需求跟踪是顶层节点,下面就是需求了,直接把需求绑定到顶层的child
data.child = res.issueTrackDTOList;
} else {
// data是顶层的需求跟踪
data.applyList = [];
data.summary = '-';
data.issueId = '-1';
// 需求跟踪是顶层节点,下面就是需求了,直接把需求绑定到顶层的child
data.child = res.issueTrackDTOList;
}
let resd = await this.handleTreeData([data]);
return resd;
}
// 将 show 设置为 true, 并且加上一个唯一键
handleTreeData = async (arr) => {
for (var i in arr) {
if (arr[i].testCaseTrackDTOList) {
arr[i].child = arr[i].testCaseTrackDTOList ? arr[i].testCaseTrackDTOList : [];
}
if (arr[i].testExecuteTrackDTOS) {
arr[i].child = arr[i].testExecuteTrackDTOS;
}
if (arr[i].defects && arr[i].cycleId) {
arr[i].child = [];
}
if (arr[i].child) {
await this.handleTreeData(arr[i].child)
}
}
return arr;
}
// 将 show 设置为 true, 并且加上一个唯一键
setShow(arr) {
for (var i in arr) {
arr[i].show = false;
this.onlyKey = this.onlyKey + 1;
arr[i].onlyKey = this.onlyKey;
if (arr[i].defects && arr[i].child) {
// 将所有的缺陷绑定到 child
arr[i].child = arr[i].defects.concat(arr[i].child);
} else if (arr[i].defects) {
arr[i].child = arr[i].defects;
}
if (arr[i].child) {
this.setShow(arr[i].child)
}
}
return arr;
}
render() {
const allHeight = 25;
const { dataList, showList } = this.state;
const urlParams = AppState.currentMenuType;
return (
<Page
className="c7ntest-report-story"
service={['agile-service.issue.deleteIssue', 'agile-service.issue.listIssueWithSub']}
>
<Header
title="需求跟踪详情"
backPath={`/agile/issue-follow?type=project&id=${urlParams.id}&name=${urlParams.name}&category=undefined&organizationId=${urlParams.organizationId}`}
>
</Header>
<Content
title={this.state.currentIssue.typeCode === "issue_apply" ? `需求申请的“${this.state.currentIssue.summary}”的跟踪报表` : `需求的“${this.state.currentIssue.summary}”的跟踪报表`}
>
<div style={{ display: 'flex', flex: 1, overflow: 'hidden' }}>
<div
className="c7n-content-issue"
style={{
display: 'block',
position: 'relative',
padding: '0px 18px',
overflowY: 'scroll',
}}
>
<div className="issue-follow-detial-container">
<div>
<div className="c7n-table-thead" >
<div className="c7n-table-thead-tr" style={{marginLeft: '5px'}}>{'需求申请'} </div>
<div className="c7n-table-thead-tr" style={{marginLeft: '190px'}}>{'需求'} </div>
<div className="c7n-table-thead-tr" style={{marginLeft: '225px'}}>{'测试用例'} </div>
<div className="c7n-table-thead-tr" style={{marginLeft: '185px'}}>{'测试执行'} </div>
<div className="c7n-table-thead-tr" style={{marginLeft: '215px'}}>{'缺陷'} </div>
</div>
</div>
{
dataList && dataList.map((item, index) => {
const { showList } = this.state;
return (
<div
key={index}
className={`
issue-follow-detial-container-class
issue-follow-detial-container-class-first
${item.typeCode === 'bug' ? 'first-only-bug' : ''}
`}
style={{width: '1300px'}}
>
<div
className="issue-follow-detial-container-summary"
>
<div className="issue-follow-detial-container-summary-text">
{
item && item.applyList && item.applyList.map((record, indexd) => {
return (
<div>
{getName(record, 'apply')}
{DemandStatusName(record)}
</div>
)
})
}
{!item.applyList.length ? '-' : null }
</div>
</div>
<One record={item} show={true} style={{ marginLeft: '50px' }} />
</div>
)
})
}
</div>
</div>
</div>
</Content>
</Page>
);
}
}
class One extends Component {
constructor(props) {
super(props);
this.state = {
showList: []
}
}
componentDidMount() {
const { record } = this.props;
let tempShowList = [];
record.child && record.child.map(() => {
tempShowList.push(false)
})
this.setState({
showList: tempShowList
})
}
render() {
const { record, show, style } = this.props;
const { showList } = this.state;
return (
<div style={{ ...style }}>
{
show && record.child && record.child.map((item, index) => {
return (
<div className="issue-follow-detial-container-class" key={index} >
<div
className={`
issue-follow-detial-container-summary
${item.typeCode === 'bug' ? 'first-only-bug' : ''}
`}
>
{
item.child && item.child.length ? (
<span
onClick={() => {
const { showList } = this.state;
showList[index] = !showList[index];
this.setState({ showList: showList });
}}
>
{getArrow(showList[index], item)}
</span>
) : (
<span
style={{display: 'inline-block', width: '18px'}}
/>
)
}
<div>
<div className="issue-follow-detial-container-summary-text">{getName(item, 'agile')}</div>
{getIssueStatus(item)}
</div>
</div>
<Tow record={item} show={showList.length ? showList[index] : false} style={{ marginLeft: '50px' }} />
</div>
)
})
}
{getStatus(record, show, 'one', exStatus)}
</div>
)
}
}
class Tow extends Component {
constructor(props) {
super(props);
this.state = {
showList: []
}
}
componentDidMount() {
const { record } = this.props;
let tempShowList = [];
record.child && record.child.map(() => {
tempShowList.push(false)
})
this.setState({
showList: tempShowList
})
}
render() {
const { record, show, style } = this.props;
const { showList } = this.state;
return (
<div style={{ ...style }}>
{
show && record.child && record.child.map((item, index) => {
return (
<div className="issue-follow-detial-container-class" key={index}>
<div
className={`
issue-follow-detial-container-summary
${item.typeCode === 'bug' ? 'second-only-bug' : ''}
`}
>
{
item.child && item.child.length ? (
<span
onClick={() => {
const { showList } = this.state;
showList[index] = !showList[index];
this.setState({ showList: showList });
}}
>
{getArrow(showList[index], item)}
</span>
) : (
<span
style={{display: 'inline-block', width: '18px'}}
/>
)
}
<div>
<div className="issue-follow-detial-container-summary-text">{getName(item, 'test')}</div>
{getIssueStatus(item)}
</div>
</div>
<Three record={item} show={showList.length ? showList[index] : false} style={{ marginLeft: '50px' }} />
</div>
)
})
}
{getStatus(record, show, 'tow', exStatus)}
</div>
)
}
}
class Three extends Component {
constructor(props) {
super(props);
this.state = {
showList: []
}
}
componentDidMount() {
const { record } = this.props;
let tempShowList = [];
record.child && record.child.map(() => {
tempShowList.push(false)
})
this.setState({
showList: tempShowList
})
}
render() {
const { record, show, style } = this.props;
const { showList } = this.state;
return (
<div style={{ ...style }}>
{
show && record.child && record.child.map((item, index) => {
const status = _.find(exStatus, { statusId: item.executionStatus })
return (
<div
className={`
issue-follow-detial-container-summary
${item.typeCode === 'bug' ? 'three-only-bug' : ''}
`}
key={index}>
<div
className="issue-follow-detial-container-summary"
>
{
item.child && item.child.length ? (
<span
onClick={() => {
const { showList } = this.state;
showList[index] = !showList[index];
this.setState({ showList: showList });
}}
>
{getArrow(showList[index], item)}
</span>
) : (
<span
style={{display: 'inline-block', width: '18px'}}
/>
)
}
<div>
<div className="issue-follow-detial-container-summary-text">{getName(item, 'execute')}</div>
{status ? getExecuteStatus(status) : getIssueStatus(item)}
</div>
</div>
<Four record={item} show={showList.length ? showList[index] : false} style={{ marginLeft: '50px' }} />
</div>
)
})
}
{getStatus(record, show, 'three', exStatus)}
</div>
)
}
}
class Four extends Component {
constructor(props) {
super(props);
this.state = {
showList: []
}
}
componentDidMount() {
const { record } = this.props;
let tempShowList = [];
record.child && record.child.map(() => {
tempShowList.push(false)
})
this.setState({
showList: tempShowList
})
}
render() {
const { record, show, style } = this.props;
const { showList } = this.state;
return (
<div style={{ ...style }}>
{
show && record.child && record.child.map((item, index) => {
return (
<div
className="issue-follow-detial-container-class"
key={index}
style={{ marginLeft: 40 }}
>
<div className="issue-follow-detial-container-summary">
<div>
<div className="issue-follow-detial-container-summary-text">{getName(item, 'agile')}</div>
{getIssueStatus(item)}
</div>
</div>
</div>
)
})
}
{getStatus(record, show, 'four', exStatus)}
</div>
)
}
}
export default Form.create()(Issue);
展示个人认为有挑战性的前端页面
最新推荐文章于 2024-08-20 08:43:31 发布