Unverified Commit 62d65ab4 authored by Anurag Hazra's avatar Anurag Hazra Committed by GitHub
Browse files

refactor: refactor repo card (#1325)

* refactor: refactored repo-card

* test: fix tests

* test: fixed tests

* fix: unprovided description error
parent ec8eb0c8
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -53,7 +53,7 @@ module.exports = async (req, res) => {
    and if both are zero we are not showing the stats
    so we can just make the cache longer, since there is no need to frequent updates
  */
    const stars = repoData.stargazers.totalCount;
    const stars = repoData.starCount;
    const forks = repoData.forkCount;
    const isBothOver1K = stars > 1000 && forks > 1000;
    const isBothUnder1 = stars < 1 && forks < 1;
+70 −59
Original line number Diff line number Diff line
const toEmoji = require("emoji-name-map");
const {
  kFormatter,
  encodeHTML,
@@ -6,21 +5,75 @@ const {
  flexLayout,
  wrapTextMultiline,
  measureText,
  parseEmojis,
} = require("../common/utils");
const I18n = require("../common/I18n");
const Card = require("../common/Card");
const icons = require("../common/icons");
const { repoCardLocales } = require("../translations");

/**
 * @param {string} label
 * @param {string} textColor
 * @returns {string}
 */
const getBadgeSVG = (label, textColor) => `
  <g data-testid="badge" class="badge" transform="translate(320, -18)">
    <rect stroke="${textColor}" stroke-width="1" width="70" height="20" x="-12" y="-14" ry="10" rx="10"></rect>
    <text
      x="23" y="-5"
      alignment-baseline="central"
      dominant-baseline="central"
      text-anchor="middle"
      fill="${textColor}"
    >
      ${label}
    </text>
  </g>
`;

/**
 * @param {string} langName
 * @param {string} langColor
 * @returns {string}
 */
const createLanguageNode = (langName, langColor) => {
  return `
  <g data-testid="primary-lang">
    <circle data-testid="lang-color" cx="0" cy="-5" r="6" fill="${langColor}" />
    <text data-testid="lang-name" class="gray" x="15">${langName}</text>
  </g>
  `;
};

const ICON_SIZE = 16;
const iconWithLabel = (icon, label, testid) => {
  if (label <= 0) return "";
  const iconSvg = `
    <svg
      class="icon"
      y="-12"
      viewBox="0 0 16 16"
      version="1.1"
      width="${ICON_SIZE}"
      height="${ICON_SIZE}"
    >
      ${icon}
    </svg>
  `;
  const text = `<text data-testid="${testid}" class="gray">${label}</text>`;
  return flexLayout({ items: [iconSvg, text], gap: 20 }).join("");
};

const renderRepoCard = (repo, options = {}) => {
  const {
    name,
    nameWithOwner,
    description,
    primaryLanguage,
    stargazers,
    isArchived,
    isTemplate,
    starCount,
    forkCount,
  } = repo;
  const {
@@ -36,22 +89,17 @@ const renderRepoCard = (repo, options = {}) => {
    locale,
  } = options;

  const lineHeight = 10;
  const header = show_owner ? nameWithOwner : name;
  const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified";
  const langColor = (primaryLanguage && primaryLanguage.color) || "#333";

  const shiftText = langName.length > 15 ? 0 : 30;

  let desc = description || "No description provided";

  // parse emojis to unicode
  desc = desc.replace(/:\w+:/gm, (emoji) => {
    return toEmoji.get(emoji) || "";
  });

  const desc = parseEmojis(description || "No description provided");
  const multiLineDescription = wrapTextMultiline(desc);
  const descriptionLines = multiLineDescription.length;
  const lineHeight = 10;
  const descriptionSvg = multiLineDescription
    .map((line) => `<tspan dy="1.2em" x="25">${encodeHTML(line)}</tspan>`)
    .join("");

  const height =
    (descriptionLines > 1 ? 120 : 110) + descriptionLines * lineHeight;
@@ -72,56 +120,21 @@ const renderRepoCard = (repo, options = {}) => {
      theme,
    });

  const totalStars = kFormatter(stargazers.totalCount);
  const totalForks = kFormatter(forkCount);

  const getBadgeSVG = (label) => `
    <g data-testid="badge" class="badge" transform="translate(320, -18)">
      <rect stroke="${textColor}" stroke-width="1" width="70" height="20" x="-12" y="-14" ry="10" rx="10"></rect>
      <text
        x="23" y="-5"
        alignment-baseline="central"
        dominant-baseline="central"
        text-anchor="middle"
        fill="${textColor}"
      >
        ${label}
      </text>
    </g>
  `;

  const svgLanguage = primaryLanguage
    ? `
    <g data-testid="primary-lang">
      <circle data-testid="lang-color" cx="0" cy="-5" r="6" fill="${langColor}" />
      <text data-testid="lang-name" class="gray" x="15">${langName}</text>
    </g>
    `
    ? createLanguageNode(langName, langColor)
    : "";

  const iconSize = 16;
  const iconWithLabel = (icon, label, testid) => {
    const iconSvg = `
      <svg class="icon" y="-12" viewBox="0 0 16 16" version="1.1" width="${iconSize}" height="${iconSize}">
        ${icon}
      </svg>
    `;
    const text = `<text data-testid="${testid}" class="gray">${label}</text>`;
    return flexLayout({ items: [iconSvg, text], gap: 20 }).join("");
  };

  const svgStars =
    stargazers.totalCount > 0 &&
    iconWithLabel(icons.star, totalStars, "stargazers");
  const svgForks =
    forkCount > 0 && iconWithLabel(icons.fork, totalForks, "forkcount");
  const totalStars = kFormatter(starCount);
  const totalForks = kFormatter(forkCount);
  const svgStars = iconWithLabel(icons.star, totalStars, "stargazers");
  const svgForks = iconWithLabel(icons.fork, totalForks, "forkcount");

  const starAndForkCount = flexLayout({
    items: [svgLanguage, svgStars, svgForks],
    sizes: [
      measureText(langName, 12),
      iconSize + measureText(`${totalStars}`, 12),
      iconSize + measureText(`${totalForks}`, 12),
      ICON_SIZE + measureText(`${totalStars}`, 12),
      ICON_SIZE + measureText(`${totalForks}`, 12),
    ],
    gap: 25,
  }).join("");
@@ -155,16 +168,14 @@ const renderRepoCard = (repo, options = {}) => {
  return card.render(`
    ${
      isTemplate
        ? getBadgeSVG(i18n.t("repocard.template"))
        ? getBadgeSVG(i18n.t("repocard.template"), textColor)
        : isArchived
        ? getBadgeSVG(i18n.t("repocard.archived"))
        ? getBadgeSVG(i18n.t("repocard.archived"), textColor)
        : ""
    }

    <text class="description" x="25" y="-5">
      ${multiLineDescription
        .map((line) => `<tspan dy="1.2em" x="25">${encodeHTML(line)}</tspan>`)
        .join("")}
      ${descriptionSvg}
    </text>

    <g transform="translate(30, ${height - 75})">
+19 −4
Original line number Diff line number Diff line
const axios = require("axios");
const wrap = require("word-wrap");
const themes = require("../../themes");
const toEmoji = require("emoji-name-map");

const renderError = (message, secondaryMessage = "") => {
  return `
@@ -88,10 +89,11 @@ function request(data, headers) {
}

/**
 *
 * @param {string[]} items
 * @param {Number} gap
 * @param {"column" | "row"} direction
 * @param {object} props
 * @param {string[]} props.items
 * @param {number} props.gap
 * @param {number[]} props.sizes
 * @param {"column" | "row"} props.direction
 *
 * @returns {string[]}
 *
@@ -257,6 +259,18 @@ function chunkArray(arr, perChunk) {
  }, []);
}

/**
 *
 * @param {string} str
 * @returns {string}
 */
function parseEmojis(str) {
  if (!str) throw new Error("[parseEmoji]: str argument not provided");
  return str.replace(/:\w+:/gm, (emoji) => {
    return toEmoji.get(emoji) || "";
  });
}

module.exports = {
  renderError,
  kFormatter,
@@ -276,4 +290,5 @@ module.exports = {
  CustomError,
  lowercaseTrim,
  chunkArray,
  parseEmojis,
};
+8 −2
Original line number Diff line number Diff line
@@ -63,7 +63,10 @@ async function fetchRepo(username, reponame) {
    if (!data.user.repository || data.user.repository.isPrivate) {
      throw new Error("User Repository Not found");
    }
    return data.user.repository;
    return {
      ...data.user.repository,
      starCount: data.user.repository.stargazers.totalCount,
    };
  }

  if (isOrg) {
@@ -73,7 +76,10 @@ async function fetchRepo(username, reponame) {
    ) {
      throw new Error("Organization Repository Not found");
    }
    return data.organization.repository;
    return {
      ...data.organization.repository,
      starCount: data.organization.repository.stargazers.totalCount,
    };
  }
}

+11 −4
Original line number Diff line number Diff line
@@ -19,14 +19,14 @@ const data_repo = {

const data_user = {
  data: {
    user: { repository: data_repo },
    user: { repository: data_repo.repository },
    organization: null,
  },
};
const data_org = {
  data: {
    user: null,
    organization: { repository: data_repo },
    organization: { repository: data_repo.repository },
  },
};

@@ -41,14 +41,21 @@ describe("Test fetchRepo", () => {
    mock.onPost("https://api.github.com/graphql").reply(200, data_user);

    let repo = await fetchRepo("anuraghazra", "convoychat");
    expect(repo).toStrictEqual(data_repo);

    expect(repo).toStrictEqual({
      ...data_repo.repository,
      starCount: data_repo.repository.stargazers.totalCount,
    });
  });

  it("should fetch correct org repo", async () => {
    mock.onPost("https://api.github.com/graphql").reply(200, data_org);

    let repo = await fetchRepo("anuraghazra", "convoychat");
    expect(repo).toStrictEqual(data_repo);
    expect(repo).toStrictEqual({
      ...data_repo.repository,
      starCount: data_repo.repository.stargazers.totalCount,
    });
  });

  it("should throw error if user is found but repo is null", async () => {
Loading