ICTU/quality-time

View on GitHub
components/frontend/src/metric/MetricConfigurationParameters.js

Summary

Maintainability
C
7 hrs
Test Coverage
import { func, string } from "prop-types"
import { useContext } from "react"
import { Grid, Header } from "semantic-ui-react"

import { set_metric_attribute } from "../api/metric"
import { DataModel } from "../context/DataModel"
import { EDIT_REPORT_PERMISSION } from "../context/Permissions"
import { MultipleChoiceInput } from "../fields/MultipleChoiceInput"
import { SingleChoiceInput } from "../fields/SingleChoiceInput"
import { StringInput } from "../fields/StringInput"
import { metricPropType, reportPropType, subjectPropType } from "../sharedPropTypes"
import {
    dropdownOptions,
    formatMetricScale,
    getMetricDirection,
    getMetricScale,
    getMetricTags,
    getReportTags,
} from "../utils"
import { LabelWithHelp } from "../widgets/LabelWithHelp"
import { MetricType } from "./MetricType"
import { Target } from "./Target"

function metric_scale_options(metric_scales, dataModel) {
    let scale_options = []
    metric_scales.forEach((scale) => {
        let scale_name = dataModel.scales ? dataModel.scales[scale].name : "Count"
        let scale_description = dataModel.scales ? dataModel.scales[scale].description : ""
        scale_options.push({
            content: <Header as="h4" content={scale_name} subheader={scale_description} />,
            key: scale,
            text: scale_name,
            value: scale,
        })
    })
    return scale_options
}

function MetricName({ metric, metric_uuid, reload }) {
    const dataModel = useContext(DataModel)
    const metricType = dataModel.metrics[metric.type]
    const labelId = `metric-name-${metric_uuid}`
    return (
        <StringInput
            aria-labelledby={labelId}
            requiredPermissions={[EDIT_REPORT_PERMISSION]}
            label={<label id={labelId}>Metric name</label>}
            placeholder={metricType.name}
            set_value={(value) => set_metric_attribute(metric_uuid, "name", value, reload)}
            value={metric.name ?? ""}
        />
    )
}
MetricName.propTypes = {
    metric: metricPropType,
    metric_uuid: string,
    reload: func,
}

function Tags({ metric, metric_uuid, reload, report }) {
    const tags = getReportTags(report)
    const labelId = `tags-${metric_uuid}`
    return (
        <MultipleChoiceInput
            allowAdditions
            aria-labelledby={labelId}
            label={<label id={labelId}>Tags</label>}
            options={dropdownOptions(tags)}
            requiredPermissions={[EDIT_REPORT_PERMISSION]}
            set_value={(value) => set_metric_attribute(metric_uuid, "tags", value, reload)}
            value={getMetricTags(metric)}
        />
    )
}
Tags.propTypes = {
    metric: metricPropType,
    metric_uuid: string,
    reload: func,
    report: reportPropType,
}

function Scale({ metric, metric_uuid, reload }) {
    const dataModel = useContext(DataModel)
    const scale = getMetricScale(metric, dataModel)
    const metricType = dataModel.metrics[metric.type]
    const scale_options = metric_scale_options(metricType.scales || ["count"], dataModel)
    const labelId = `scale-${metric_uuid}`
    return (
        <SingleChoiceInput
            aria-labelledby={labelId}
            requiredPermissions={[EDIT_REPORT_PERMISSION]}
            label={<label id={labelId}>Metric scale</label>}
            options={scale_options}
            placeholder={metricType.default_scale || "Count"}
            set_value={(value) => set_metric_attribute(metric_uuid, "scale", value, reload)}
            value={scale}
        />
    )
}
Scale.propTypes = {
    metric: metricPropType,
    metric_uuid: string,
    reload: func,
}

function Direction({ metric, metric_uuid, reload }) {
    const dataModel = useContext(DataModel)
    const scale = getMetricScale(metric, dataModel)
    const metricType = dataModel.metrics[metric.type]
    const metricUnitWithoutPercentage = metric.unit || metricType.unit
    const metricUnit = `${scale === "percentage" ? "% " : ""}${metricUnitWithoutPercentage}`
    const fewer = {
        count: `Fewer ${metricUnit}`,
        percentage: `A lower percentage of ${metricUnitWithoutPercentage}`,
        version_number: "A lower version number",
    }[scale]
    const more = {
        count: `More ${metricUnit}`,
        percentage: `A higher percentage of ${metricUnitWithoutPercentage}`,
        version_number: "A higher version number",
    }[scale]
    const metricDirection = getMetricDirection(metric, dataModel) ?? "<"
    const labelId = `direction-${metric_uuid}`
    return (
        <SingleChoiceInput
            aria-labelledby={labelId}
            requiredPermissions={[EDIT_REPORT_PERMISSION]}
            label={<label id={labelId}>Metric direction</label>}
            options={[
                { key: "0", text: `${fewer} is better`, value: "<" },
                { key: "1", text: `${more} is better`, value: ">" },
            ]}
            set_value={(value) => set_metric_attribute(metric_uuid, "direction", value, reload)}
            value={metricDirection}
        />
    )
}
Direction.propTypes = {
    metric: metricPropType,
    metric_uuid: string,
    reload: func,
}

function Unit({ metric, metric_uuid, reload }) {
    const dataModel = useContext(DataModel)
    const metricType = dataModel.metrics[metric.type]
    const labelId = `unit-${metric_uuid}`
    return (
        <StringInput
            aria-labelledby={labelId}
            label={<label id={labelId}>Metric unit</label>}
            placeholder={metricType.unit}
            prefix={formatMetricScale(metric, dataModel)}
            requiredPermissions={[EDIT_REPORT_PERMISSION]}
            set_value={(value) => set_metric_attribute(metric_uuid, "unit", value, reload)}
            value={metric.unit ?? ""}
        />
    )
}
Unit.propTypes = {
    metric: metricPropType,
    metric_uuid: string,
    reload: func,
}

function EvaluateTargets({ metric, metric_uuid, reload }) {
    const help =
        "Turning off evaluation of the metric targets makes this an informative metric. Informative metrics do not turn red, green, or yellow, and can't have accepted technical debt."
    const labelId = `evaluate-targets-label-${metric_uuid}`
    return (
        <SingleChoiceInput
            aria-labelledby={labelId}
            requiredPermissions={[EDIT_REPORT_PERMISSION]}
            label={<LabelWithHelp labelId={labelId} label="Evaluate metric targets?" help={help} />}
            value={metric.evaluate_targets ?? true}
            options={[
                { key: true, text: "Yes", value: true },
                { key: false, text: "No", value: false },
            ]}
            set_value={(value) => set_metric_attribute(metric_uuid, "evaluate_targets", value, reload)}
        />
    )
}
EvaluateTargets.propTypes = {
    metric: metricPropType,
    metric_uuid: string,
    reload: func,
}

export function MetricConfigurationParameters({ metric, metric_uuid, reload, report, subject }) {
    const dataModel = useContext(DataModel)
    const metricScale = getMetricScale(metric, dataModel)
    return (
        <Grid stackable columns={3}>
            <Grid.Row>
                <Grid.Column>
                    <MetricType
                        subjectType={subject.type}
                        metricType={metric.type}
                        metric_uuid={metric_uuid}
                        reload={reload}
                    />
                </Grid.Column>
                <Grid.Column>
                    <MetricName metric={metric} metric_uuid={metric_uuid} reload={reload} />
                </Grid.Column>
                <Grid.Column>
                    <Tags metric={metric} metric_uuid={metric_uuid} reload={reload} report={report} />
                </Grid.Column>
            </Grid.Row>
            <Grid.Row>
                <Grid.Column>
                    <Scale metric={metric} metric_uuid={metric_uuid} reload={reload} />
                </Grid.Column>
                <Grid.Column>
                    <Direction metric={metric} metric_uuid={metric_uuid} reload={reload} />
                </Grid.Column>
                {metricScale !== "version_number" && (
                    <Grid.Column>
                        <Unit metric={metric} metric_uuid={metric_uuid} reload={reload} />
                    </Grid.Column>
                )}
            </Grid.Row>
            <Grid.Row>
                <Grid.Column>
                    <EvaluateTargets metric={metric} metric_uuid={metric_uuid} reload={reload} />
                </Grid.Column>
                <Grid.Column>
                    <Target
                        label="Metric target"
                        labelPosition="top center"
                        target_type="target"
                        metric={metric}
                        metric_uuid={metric_uuid}
                        reload={reload}
                    />
                </Grid.Column>
                <Grid.Column>
                    <Target
                        label="Metric near target"
                        labelPosition="top right"
                        target_type="near_target"
                        metric={metric}
                        metric_uuid={metric_uuid}
                        reload={reload}
                    />
                </Grid.Column>
            </Grid.Row>
        </Grid>
    )
}
MetricConfigurationParameters.propTypes = {
    metric: metricPropType,
    metric_uuid: string,
    reload: func,
    report: reportPropType,
    subject: subjectPropType,
}