packages/issue-dashboard-widgets/widgets/youtrack-issues-list/app/issue-line.js (247 lines of code) (raw):

import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import fecha from 'fecha'; import Link from '@jetbrains/ring-ui/components/link/link'; import Tooltip from '@jetbrains/ring-ui/components/tooltip/tooltip'; import { ChevronUpIcon, ChevronDownIcon } from '@jetbrains/ring-ui/components/icon'; import './style/issues-list-widget.css'; class IssueLine extends React.Component { static fieldColorToCss(color) { return { background: color.background, color: color.foreground }; } static getValuableIssueFields(issue) { return (issue.fields || []).filter( field => IssueLine.toArray(field.value || []).length > 0 ).filter( field => { const valueType = field.projectCustomField && field.projectCustomField.field && field.projectCustomField.field.fieldType && field.projectCustomField.field.fieldType.valueType; return valueType !== 'text'; } ); } static getName(field) { return field.localizedName || field.name; } static getDatePresentation(timestamp, dateFormats, withTime) { return fecha.format( timestamp, withTime ? dateFormats.dateTimePattern : dateFormats.datePattern ); } static getValuePresentation(issueField, dateFromats) { const field = issueField.projectCustomField && issueField.projectCustomField.field; const fieldType = (field && field.fieldType && field.fieldType.valueType) || ''; return IssueLine.toArray(issueField.value || []).map(value => { if (fieldType.indexOf('date') > -1) { return IssueLine.getDatePresentation( value, dateFromats, fieldType.indexOf('time') > -1 ); } return IssueLine.getName(value) || value.presentation || value.minutes || value.name || value.login || value; }).join(', '); } static toArray = value => (Array.isArray(value) ? value : [value]); static getFirstLetter = value => (IssueLine.getName(value) || 'c')[0].toUpperCase(); static isColoredValue = value => value.color && value.color.id > 0; static getColoredSquareModel(issue) { const makeColorFieldPresentationObject = issueField => { const coloredValue = IssueLine.toArray(issueField.value).filter( IssueLine.isColoredValue )[0]; if (!coloredValue) { return null; } const fieldName = IssueLine.getName( issueField.projectCustomField.field || {} ); return { style: IssueLine.fieldColorToCss(coloredValue.color), letter: IssueLine.getFirstLetter(coloredValue), title: `${fieldName}: ${IssueLine.getName(coloredValue)}`, issueField }; }; const bundleFields = (issue.fields || []).filter( issueField => !!issueField.projectCustomField.bundle ); const priorityField = bundleFields.filter( issueField => { const field = issueField.projectCustomField.field || {}; return (field.name || '').toLowerCase() === 'priority'; } )[0]; if (priorityField) { if (priorityField.value) { return makeColorFieldPresentationObject(priorityField); } return null; } const fieldWithColoredValues = (issue.fields || []).filter( field => IssueLine.toArray(field.value || []).some(IssueLine.isColoredValue) )[0]; if (!fieldWithColoredValues) { return null; } return makeColorFieldPresentationObject(fieldWithColoredValues); } static onOpenIssue = evt => evt.stopPropagation(); static propTypes = { issue: PropTypes.object, homeUrl: PropTypes.string, expanded: PropTypes.bool, dateFormats: PropTypes.object }; static defaultProps = { dateFormats: { datePattern: 'YYYY-MM-DD', dateTimePattern: 'YYYY-MM-DD\'T\'HH:mm:ss' } }; constructor(props) { super(props); const {issue, expanded} = this.props; this.state = { issue, expanded, coloredSquare: IssueLine.getColoredSquareModel(issue), valuableFields: IssueLine.getValuableIssueFields(issue) }; } static getDerivedStateFromProps(props) { const {issue, expanded} = props; return { issue, expanded, coloredSquare: IssueLine.getColoredSquareModel(issue), valuableFields: IssueLine.getValuableIssueFields(issue) }; } renderFieldValue(issueField) { const firstValue = IssueLine.toArray(issueField.value)[0]; return ( <div className="issues-list-widget__field-value"> {IssueLine.getValuePresentation(issueField, this.props.dateFormats)} { firstValue.avatarUrl && ( <img className="issues-list-widget__field-avatar" src={firstValue.avatarUrl} /> ) } { IssueLine.isColoredValue(firstValue) && ( <span className="issues-list-widget__field-color issues-list-widget__colored-field" style={IssueLine.fieldColorToCss(firstValue.color)} > {IssueLine.getFirstLetter(firstValue)} </span> ) } </div> ); } renderFields(issueFields, fixed) { const fixClassName = fixed ? 'issues-list-widget__fields-fix' : ''; return ( <div className={`issues-list-widget__fields ${fixClassName}`}> { issueFields.map(issueField => ( <div key={`field-line-${issueField.id}`} className="issues-list-widget__field-row" > <div className="issues-list-widget__field"> <div className="issues-list-widget__field-title"> {IssueLine.getName(issueField.projectCustomField.field)} </div> {this.renderFieldValue(issueField)} </div> </div> )) } </div> ); } render() { const { issue, coloredSquare, valuableFields, expanded, highlighted } = this.state; const {homeUrl} = this.props; const normalizedHomeUrl = homeUrl.charAt(homeUrl.length - 1) === '/' ? homeUrl : `${homeUrl}/`; const makeHighlighted = flag => () => this.setState({highlighted: flag}); const getIssueLinkClassName = baseClassName => { const resolved = issue.resolved !== undefined && issue.resolved !== null; return classNames( baseClassName, resolved && `${baseClassName}_resolved` ); }; const getIssueLineClassName = () => classNames( 'issues-list-widget__issue', expanded && 'issues-list-widget__issue_expanded', highlighted && 'issues-list-widget__issue_highlighted' ); return ( <div className={getIssueLineClassName()} onMouseOver={makeHighlighted(true)} onMouseLeave={makeHighlighted(false)} > { coloredSquare && ( <span className={'issues-list-widget__colored-field'} style={coloredSquare.style} > <Tooltip title={this.renderFields([coloredSquare.issueField], true)} > {coloredSquare.letter} </Tooltip> </span> ) } <div className="issues-list-widget__issue-info" onClick={IssueLine.onOpenIssue} > <Link className={ getIssueLinkClassName('issues-list-widget__issue-id') } href={`${normalizedHomeUrl}issue/${issue.idReadable}`} target="_blank" > {issue.idReadable} </Link> <Link key={`issue-summary-${issue.id}`} className={ getIssueLinkClassName('issues-list-widget__issue-summary') } href={`${normalizedHomeUrl}issue/${issue.idReadable}`} target="_blank" > {issue.summary} </Link> </div> <div className="issues-list-widget__issue-toggler"> { expanded ? ( <ChevronUpIcon size={ChevronUpIcon.Size.Size14} color={ChevronUpIcon.Color.GRAY} /> ) : ( <ChevronDownIcon size={ChevronDownIcon.Size.Size14} color={ChevronDownIcon.Color.GRAY} /> ) } </div> { expanded && ( <div className="issues-list-widget__issue-expanded-block" data-test="issue-line-expanded-block" > {this.renderFields(valuableFields)} </div> ) } </div> ); } } export default IssueLine;