All files / webapp/model pamFilters.ts

100% Statements 27/27
92.85% Branches 13/14
100% Functions 10/10
100% Lines 27/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176                                        47x                 47x                               47x                                                       47x                       47x                               300x 466210x   466210x   279126x   300x   300x 497857x 496897x 496897x   496897x 495817x       2040x           300x         66543x                                                       2x   2x 1559x   1559x       1559x     2x 10x 14x    
import { parseGermanDate } from "./germanDate";
import { PamProduct } from "./productKey";
 
/** One entry of a filter `Select`'s dropdown, as bound to `key`/`filter>text`. */
export interface FilterOption {
    key: string;
    text: string;
}
 
/** Maps a filter control's view id to the PAM field path it filters on. */
export interface FilterFieldDefinition {
    id: string;
    path: string;
}
 
/**
 * Search/filter configuration of the PAM product table, previously
 * duplicated identically across the Main and SupportStatus controllers.
 * Fields matched by the global full-text search (`globalSearch`).
 */
export const SEARCH_FIELDS: string[] = [
    "version",
    "name",
    "product",
    "line",
    "category"
];
 
/** Free-text filter inputs, keyed by their view control id. */
export const TEXT_FILTERS: FilterFieldDefinition[] = [
    {
        id: "filterProductVersion",
        path: "version"
    },
    {
        id: "filterProduct",
        path: "product"
    },
    {
        id: "filterProductLine",
        path: "line"
    }
];
 
/** Dropdown (exact-match) filters, keyed by their view control id. */
export const SELECT_FILTERS: FilterFieldDefinition[] = [
    {
        id: "filterCurrentStatus",
        path: "status"
    },
    {
        id: "filterUnrestrictedAvailable",
        path: "ua"
    },
    {
        id: "filterRestrictedAvailableNotAllowed",
        path: "ra_puna"
    },
    {
        id: "filterMainstreamMaintenance",
        path: "eomm"
    },
    {
        id: "filterExtendedMaintenance",
        path: "eom"
    }
];
 
/**
 * PAM fields holding dd.mm.yyyy dates (or occasionally unparseable
 * free-text dates, see {@link parseGermanDate}). Used to pick chronological
 * instead of alphabetical sorting/filter-option ordering.
 */
export const DATE_FIELDS: string[] = [
    "ra_puna",
    "ra_pua",
    "ua",
    "hanaDate",
    "eomm",
    "eom",
    "eop1",
    "eosk"
];
 
/** PAM field used by the "mainstream maintenance ends within X months" relative filter. */
export const MAINSTREAM_MAINTENANCE_FIELD = "eomm";
 
/**
 * Builds the unique, sorted values of a PAM field as `Select` options,
 * including a leading "all" option. Date fields (see {@link DATE_FIELDS})
 * are sorted chronologically instead of alphabetically.
 * @param data The PAM entries to collect values from
 * @param field The PAM field to build options for
 * @param allLabel The label for the leading "all" option
 * @returns The filter options, "all" first
 */
export function createFilterOptions(
    data: PamProduct[],
    field: string,
    allLabel: string
): FilterOption[] {
    const values = data
        .map((entry) => entry[field])
        .filter((value): value is string => {
            return typeof value === "string" && value.trim().length > 0;
        })
        .map((value) => value.trim());
 
    const uniqueValues = Array.from(new Set(values));
 
    uniqueValues.sort((value1, value2) => {
        if (DATE_FIELDS.includes(field)) {
            const date1 = parseGermanDate(value1);
            const date2 = parseGermanDate(value2);
 
            if (date1 !== null && date2 !== null) {
                return date1.getTime() - date2.getTime();
            }
        }
 
        return value1.localeCompare(value2, "de", {
            numeric: true,
            sensitivity: "base"
        });
    });
 
    return [
        {
            key: "",
            text: allLabel
        },
        ...uniqueValues.map((value) => ({
            key: value,
            text: value
        }))
    ];
}
 
/** One bar of a {@link buildFieldDistribution} chart: a field value and its entry count. */
export interface FieldDistributionEntry {
    value: string;
    count: number;
}
 
/**
 * Groups PAM entries by a field's value and counts them, for the "chart
 * analysis" bar chart on the Main and SupportStatus tables. Entries without
 * a value for the field are grouped under `unspecifiedLabel`. Sorted by
 * count, descending, so the most common values appear first.
 * @param entries The (already filtered) PAM entries to group
 * @param field The PAM field to group by
 * @param unspecifiedLabel The label to use for entries with no value for the field
 * @returns The distribution, most common value first
 */
export function buildFieldDistribution(
    entries: PamProduct[],
    field: string,
    unspecifiedLabel: string
): FieldDistributionEntry[] {
    const counts = new Map<string, number>();
 
    entries.forEach((entry) => {
        const rawValue = entry[field];
 
        const value = typeof rawValue === "string" && rawValue.trim().length > 0
            ? rawValue.trim()
            : unspecifiedLabel;
 
        counts.set(value, (counts.get(value) ?? 0) + 1);
    });
 
    return Array.from(counts.entries())
        .map(([value, count]) => ({ value, count }))
        .sort((entry1, entry2) => entry2.count - entry1.count);
}