ICTU/quality-time

View on GitHub
components/frontend/src/issue/IssueStatus.js

Summary

Maintainability
D
1 day
Test Coverage
import { bool, string } from "prop-types"
import TimeAgo from "react-timeago"

import { Label, Popup } from "../semantic_ui_react_wrappers"
import { issueStatusPropType, metricPropType, settingsPropType, stringsPropType } from "../sharedPropTypes"
import { getMetricIssueIds, ISSUE_STATUS_COLORS } from "../utils"
import { HyperLink } from "../widgets/HyperLink"
import { TimeAgoWithDate } from "../widgets/TimeAgoWithDate"

function IssueWithoutTracker({ issueId }) {
    return (
        <Popup
            content={
                "Please configure an issue tracker by expanding the report title, selecting the 'Issue tracker' tab, and configuring an issue tracker."
            }
            header={"No issue tracker configured"}
            trigger={<Label color="red">{issueId}</Label>}
        />
    )
}
IssueWithoutTracker.propTypes = {
    issueId: string,
}

function IssuesWithoutTracker({ issueIds }) {
    return (
        <>
            {issueIds.map((issueId) => (
                <IssueWithoutTracker key={issueId} issueId={issueId} />
            ))}
        </>
    )
}
IssuesWithoutTracker.propTypes = {
    issueIds: stringsPropType,
}

function labelDetails(issueStatus, settings) {
    let details = [<Label.Detail key="name">{issueStatus.name || "?"}</Label.Detail>]
    if (issueStatus.summary && settings.showIssueSummary.value) {
        details.push(<Label.Detail key="summary">{issueStatus.summary}</Label.Detail>)
    }
    if (issueStatus.created && settings.showIssueCreationDate.value) {
        details.push(
            <Label.Detail key="created">
                Created <TimeAgo date={issueStatus.created} />
            </Label.Detail>,
        )
    }
    if (issueStatus.updated && settings.showIssueUpdateDate.value) {
        details.push(
            <Label.Detail key="updated">
                Updated <TimeAgo date={issueStatus.updated} />
            </Label.Detail>,
        )
    }
    if (issueStatus.duedate && settings.showIssueDueDate.value) {
        details.push(
            <Label.Detail key="duedate">
                Due <TimeAgo date={issueStatus.duedate} />
            </Label.Detail>,
        )
    }
    if (issueStatus.release_name && settings.showIssueRelease.value) {
        details.push(releaseLabel(issueStatus))
    }
    if (issueStatus.sprint_name && settings.showIssueSprint.value) {
        details.push(sprintLabel(issueStatus))
    }
    return details
}
labelDetails.propTypes = {
    issueStatus: issueStatusPropType,
    settings: settingsPropType,
}

function releaseStatus(issueStatus) {
    return issueStatus.release_released ? "released" : "planned"
}
releaseStatus.propTypes = {
    issueStatus: issueStatusPropType,
}

function releaseLabel(issueStatus) {
    const date = issueStatus.release_date ? <TimeAgo date={issueStatus.release_date} /> : null
    return (
        <Label.Detail key="release">
            {prefixName(issueStatus.release_name, "Release")} {releaseStatus(issueStatus)} {date}
        </Label.Detail>
    )
}
releaseLabel.propTypes = {
    issueStatus: issueStatusPropType,
}

function sprintLabel(issueStatus) {
    const sprintEnd = issueStatus.sprint_enddate ? (
        <>
            ends <TimeAgo date={issueStatus.sprint_enddate} />
        </>
    ) : null
    return (
        <Label.Detail key="sprint">
            {prefixName(issueStatus.sprint_name, "Sprint")} ({issueStatus.sprint_state}) {sprintEnd}
        </Label.Detail>
    )
}
sprintLabel.propTypes = {
    issueStatus: issueStatusPropType,
}

function prefixName(name, prefix) {
    // Prefix the name with prefix unless the name already contains the prefix
    return name.toLowerCase().indexOf(prefix.toLowerCase()) < 0 ? `${prefix} ${name}` : name
}
prefixName.propType = {
    name: string,
    prefix: string,
}

function issueLabel(issueStatus, settings, error) {
    // The issue status can be unknown when the issue was added recently and the status hasn't been collected yet
    const color = error ? "red" : ISSUE_STATUS_COLORS[issueStatus.status_category ?? "unknown"]
    const label = (
        <Label basic={!error} color={color}>
            {issueStatus.issue_id}
            {labelDetails(issueStatus, settings)}
        </Label>
    )
    if (issueStatus.landing_url) {
        // Without the span, the popup doesn't work
        return (
            <span>
                <HyperLink url={issueStatus.landing_url}>{label}</HyperLink>
            </span>
        )
    }
    return label
}
issueLabel.propTypes = {
    issueStatus: issueStatusPropType,
    settings: settingsPropType,
    error: string,
}

function IssueWithTracker({ issueStatus, settings }) {
    let popupContent = "" // Will contain error if any, otherwise creation and update dates, if any, else be empty
    let popupHeader = ""
    if (issueStatus.connection_error) {
        popupHeader = "Connection error"
        popupContent = "Quality-time could not retrieve data from the issue tracker."
    }
    if (issueStatus.parse_error) {
        popupHeader = "Parse error"
        popupContent = "Quality-time could not parse the data received from the issue tracker."
    }
    let label = issueLabel(issueStatus, settings, popupHeader)
    if (!popupContent && issueStatus.created) {
        popupHeader = issueStatus.summary
        popupContent = issuePopupContent(issueStatus)
    }
    if (popupContent) {
        label = <Popup header={popupHeader} content={popupContent} flowing hoverable trigger={label} />
    }
    return label
}
IssueWithTracker.propTypes = {
    issueStatus: issueStatusPropType,
    settings: settingsPropType,
}

function issuePopupContent(issueStatus) {
    let popupContent = <TimeAgoWithDate date={issueStatus.created}>Created</TimeAgoWithDate>
    if (issueStatus.updated) {
        popupContent = (
            <>
                {popupContent}
                <br />
                <TimeAgoWithDate date={issueStatus.updated}>Updated</TimeAgoWithDate>
            </>
        )
    }
    if (issueStatus.duedate) {
        popupContent = (
            <>
                {popupContent}
                <br />
                <TimeAgoWithDate date={issueStatus.duedate}>Due</TimeAgoWithDate>
            </>
        )
    }
    if (issueStatus.release_name) {
        const releaseDate = issueStatus.release_date ? (
            <TimeAgoWithDate date={issueStatus.release_date}>{releaseStatus(issueStatus)}</TimeAgoWithDate>
        ) : null
        popupContent = (
            <>
                {popupContent}
                <br />
                {prefixName(issueStatus.release_name, "Release")} {releaseDate}
            </>
        )
    }
    if (issueStatus.sprint_name) {
        const sprintEnd = issueStatus.sprint_enddate ? (
            <TimeAgoWithDate date={issueStatus.sprint_enddate}>ends</TimeAgoWithDate>
        ) : null
        popupContent = (
            <>
                {popupContent}
                <br />
                {prefixName(issueStatus.sprint_name, "Sprint")} ({issueStatus.sprint_state}) {sprintEnd}
            </>
        )
    }
    return popupContent
}
issuePopupContent.propTypes = {
    issueStatus: issueStatusPropType,
}

function IssuesWithTracker({ issueIds, metric, settings }) {
    const issueStatuses = metric.issue_status || []
    return (
        <>
            {issueStatuses
                .filter((issueStatus) => issueIds.indexOf(issueStatus.issue_id) > -1)
                .map((issueStatus) => (
                    <IssueWithTracker key={issueStatus.issue_id} issueStatus={issueStatus} settings={settings} />
                ))}
        </>
    )
}
IssuesWithTracker.propTypes = {
    issueIds: stringsPropType,
    metric: metricPropType,
    settings: settingsPropType,
}

export function IssueStatus({ metric, issueTrackerMissing, settings }) {
    const issueIds = getMetricIssueIds(metric)
    if (issueTrackerMissing && issueIds.length > 0) {
        return <IssuesWithoutTracker issueIds={issueIds} />
    }
    return <IssuesWithTracker issueIds={issueIds} metric={metric} settings={settings} />
}
IssueStatus.propTypes = {
    issueTrackerMissing: bool,
    metric: metricPropType,
    settings: settingsPropType,
}