All files / src/api/catalog-search util.ts

94.93% Statements 75/79
83.87% Branches 52/62
91.3% Functions 21/23
94.52% Lines 69/73

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 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202                                  3x 2397x       3x 6x 2672x 2672x   2672x       2672x 194x     2478x     3x 6x 891x 701x     190x             3x   8x   8x 5x     3x 298x 435x     298x           3x 7x 98x           3x 7x 98x         3x 13x   8x 786x   786x 1393x     786x 32x 32x 24x       786x 1076x 14x       772x       3x     6x 6x                       3x                 3x                                     3x 1009x 1009x 1009x 44x 33x   1009x 3051x 1267x     1784x     1009x               3x       506x 506x     506x 251x     506x 255x   255x 255x       506x    
import { CatalogSearchFilters, ExtendedCatalogPackage } from ".";
import { CatalogSearchSort } from "./constants";
import { CDKType } from "../../constants/constructs";
import { KEYWORD_IGNORE_LIST } from "../../constants/keywords";
import { Language } from "../../constants/languages";
import { ConstructFramework, Metadata } from "../package/metadata";
import { CatalogPackage } from "../package/packages";
 
type SortFunction = (
  p1: ExtendedCatalogPackage,
  p2: ExtendedCatalogPackage
) => number;
 
type FilterFunctionBuilder<T> = (
  filter: T
) => undefined | ((pkg: ExtendedCatalogPackage) => boolean);
 
const getStrSort = (isAscending: boolean): SortFunction => {
  return (p1, p2) => p1.name.localeCompare(p2.name) * (isAscending ? 1 : -1);
};
 
const getDateSort =
  (isAscending: boolean): SortFunction =>
  (p1, p2) => {
    const d1 = new Date(p1.metadata.date).getTime();
    const d2 = new Date(p2.metadata.date).getTime();
 
    Iif (d1 === d2) {
      return getStrSort(true)(p1, p2);
    }
 
    if (isAscending) {
      return d2 < d1 ? 1 : -1;
    }
 
    return d1 < d2 ? 1 : -1;
  };
 
const getDownloadsSort = (isAscending: boolean): SortFunction => {
  return (p1, p2) => {
    if (p1.downloads !== p2.downloads) {
      return (p1.downloads - p2.downloads) * (isAscending ? 1 : -1);
    } else {
      // break ties by alphabetical
      return getStrSort(!isAscending)(p1, p2);
    }
  };
};
 
const getLanguagesFilter: FilterFunctionBuilder<
  CatalogSearchFilters["languages"]
> = (languages) => {
  const languageSet =
    (languages?.length ?? 0) > 0 ? new Set(languages) : undefined;
 
  if (!languageSet || languageSet.has(Language.TypeScript)) {
    return undefined;
  }
 
  return (pkg) => {
    const isMatched = Object.keys(pkg.languages ?? {}).some((lang) =>
      languageSet.has(lang as Language)
    );
 
    return isMatched;
  };
};
 
const getCDKTypeFilter: FilterFunctionBuilder<
  CatalogSearchFilters["cdkType"]
> = (cdkType) => {
  if (!cdkType) return undefined;
  return (pkg) => pkg.constructFrameworks.get(cdkType) !== undefined;
};
 
const getCDKMajorFilter: FilterFunctionBuilder<{
  cdkType: CatalogSearchFilters["cdkType"];
  cdkMajor: CatalogSearchFilters["cdkMajor"];
}> = ({ cdkType, cdkMajor }) => {
  if (!cdkType || typeof cdkMajor !== "number") return undefined;
  return (pkg) => pkg.constructFrameworks.get(cdkType) === cdkMajor;
};
 
const getKeywordsFilter: FilterFunctionBuilder<
  CatalogSearchFilters["keywords"]
> = (keywords) => {
  if (!keywords?.length) return undefined;
 
  return (pkg) => {
    const set = new Set<string>();
 
    for (const kw of pkg.keywords ?? []) {
      set.add(kw.toLocaleLowerCase());
    }
 
    for (const tag of pkg.metadata?.packageTags ?? []) {
      const label = tag.keyword?.label;
      if (label) {
        set.add(label.toLocaleLowerCase());
      }
    }
 
    for (const query of keywords) {
      if (set.has(query.toLocaleLowerCase())) {
        return true;
      }
    }
 
    return false;
  };
};
 
const getTagsFilter: FilterFunctionBuilder<CatalogSearchFilters["tags"]> = (
  tags
) => {
  Eif (!tags || !tags.length) {
    return undefined;
  }
 
  return (pkg) => {
    return (
      pkg.metadata?.packageTags?.some((tag) => {
        return tags.includes(tag.id);
      }) ?? false
    );
  };
};
 
export const SORT_FUNCTIONS: Record<CatalogSearchSort, SortFunction> = {
  [CatalogSearchSort.NameAsc]: getStrSort(true),
  [CatalogSearchSort.NameDesc]: getStrSort(false),
  [CatalogSearchSort.PublishDateAsc]: getDateSort(true),
  [CatalogSearchSort.PublishDateDesc]: getDateSort(false),
  [CatalogSearchSort.DownloadsAsc]: getDownloadsSort(true),
  [CatalogSearchSort.DownloadsDesc]: getDownloadsSort(false),
};
 
export const FILTER_FUNCTIONS = {
  cdkType: getCDKTypeFilter,
  cdkMajor: getCDKMajorFilter,
  keywords: getKeywordsFilter,
  languages: getLanguagesFilter,
  tags: getTagsFilter,
};
 
/**
 * Returns a set of all the keywords associated with a package. This includes
 * publisher-based keywords and keywords from package tags.
 *
 * The set contains a single entry for each keyword, and all keywords are lowercased.
 *
 * Filters out all keywords that are in the ignore list.
 *
 * @param pkg The package
 * @returns The set of keywords
 */
export const renderAllKeywords = (pkg: CatalogPackage) => {
  const allKeywords = new Set<string>();
  const publisherKeywords = pkg.keywords ?? [];
  const tagKeywords = (pkg.metadata?.packageTags ?? [])
    .filter((t) => t.keyword)
    .map((t) => t.keyword!.label);
 
  for (const kw of [...publisherKeywords, ...tagKeywords]) {
    if (KEYWORD_IGNORE_LIST.has(kw)) {
      continue;
    }
 
    allKeywords.add(kw.toLocaleLowerCase());
  }
 
  return Array.from(allKeywords);
};
 
/**
 * Creates a map of construct frameworks found in a construct's metadata
 * If the metadata doesn't specify a majorVersion, the map will return null to distinguish
 * from an undefined map.get()
 */
export const mapConstructFrameworks = ({
  constructFramework,
  constructFrameworks,
}: Metadata): Map<CDKType, number | null> => {
  const map = new Map<CDKType, number | null>();
  let frameworks = constructFrameworks ?? [];
 
  // To support the deprecated constructFramework property, re-map it to the new format
  if (!frameworks.length && constructFramework?.name) {
    frameworks = [constructFramework as ConstructFramework];
  }
 
  frameworks.forEach((framework) => {
    const { name, majorVersion } = framework;
 
    Eif (name) {
      map.set(name, majorVersion ?? null);
    }
  });
 
  return map;
};