Unverified Commit 6e73a003 authored by Willian Rodrigues's avatar Willian Rodrigues Committed by GitHub
Browse files

feat: add wakatime card (#392)



* Adds wakatime card route

* Adds language progress on wakatime card

* Adds wakatime card on README

* Adds no coding activity node

* Remove percent displayed on wakatime's card

* Update readme

* refactor: refactored code & added tests

Co-authored-by: default avatarAnurag <hazru.anurag@gmail.com>
parent e377770a
Loading
Loading
Loading
Loading

api/wakatime.js

0 → 100644
+59 −0
Original line number Diff line number Diff line
require("dotenv").config();
const {
  renderError,
  parseBoolean,
  clampValue,
  CONSTANTS,
} = require("../src/common/utils");
const { fetchLast7Days } = require("../src/fetchers/wakatime-fetcher");
const wakatimeCard = require("../src/cards/wakatime-card");

module.exports = async (req, res) => {
  const {
    username,
    title_color,
    icon_color,
    hide_border,
    line_height,
    text_color,
    bg_color,
    theme,
    cache_seconds,
    hide_title,
    hide_progress,
  } = req.query;

  res.setHeader("Content-Type", "image/svg+xml");

  try {
    const last7Days = await fetchLast7Days({ username });

    let cacheSeconds = clampValue(
      parseInt(cache_seconds || CONSTANTS.TWO_HOURS, 10),
      CONSTANTS.TWO_HOURS,
      CONSTANTS.ONE_DAY
    );

    if (!cache_seconds) {
      cacheSeconds = CONSTANTS.FOUR_HOURS;
    }

    res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);

    return res.send(
      wakatimeCard(last7Days, {
        hide_title: parseBoolean(hide_title),
        hide_border: parseBoolean(hide_border),
        line_height,
        title_color,
        icon_color,
        text_color,
        bg_color,
        theme,
        hide_progress,
      })
    );
  } catch (err) {
    return res.send(renderError(err.message, err.secondaryMessage));
  }
};
+1 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@
    "dotenv": "^8.2.0",
    "emoji-name-map": "^1.2.8",
    "github-username-regex": "^1.0.0",
    "prettier": "^2.1.2",
    "word-wrap": "^1.2.3"
  },
  "husky": {
+26 −0
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@
- [GitHub Stats Card](#github-stats-card)
- [GitHub Extra Pins](#github-extra-pins)
- [Top Languages Card](#top-languages-card)
- [Wakatime Week Stats](#wakatime-week-stats)
- [Themes](#themes)
- [Customization](#customization)
- [Deploy Yourself](#deploy-on-your-own-vercel-instance)
@@ -171,6 +172,13 @@ You can provide multiple comma-separated values in bg_color option to render a g
> Language names should be uri-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding)  
> (i.e: `c++` should become `c%2B%2B`, `jupyter notebook` should become `jupyter%20notebook`, etc.)

#### Wakatime Card Exclusive Options:

- `hide_title` - _(boolean)_
- `hide_border` - _(boolean)_
- `line_height` - Sets the line-height between text _(number)_
- `hide_progress` - Hides the progress bar and percentage _(boolean)_

---

# GitHub Extra Pins
@@ -245,6 +253,20 @@ You can use the `&layout=compact` option to change the card design.

[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats)

# Wakatime Week Stats

Change the `?username=` value to your Wakatime username.

```md
[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats)
```

### Demo

[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats)

[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats)

---

### All Demos
@@ -287,6 +309,10 @@ Choose from any of the [default themes](#themes)

[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats)

- Wakatime card

[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats)

---

### Quick Tip (Align The Repo Cards)
+156 −0
Original line number Diff line number Diff line
const { getCardColors, FlexLayout, clampValue } = require("../common/utils");
const { getStyles } = require("../getStyles");
const icons = require("../common/icons");
const Card = require("../common/Card");

const noCodingActivityNode = ({ color }) => {
  return `
    <text x="25" y="11" class="stat bold" fill="${color}">No coding activity this week</text>
  `;
};

const createProgressNode = ({
  width,
  color,
  progress,
  progressBarBackgroundColor,
}) => {
  const progressPercentage = clampValue(progress, 2, 100);

  return `
    <svg width="${width}" overflow="auto">
      <rect rx="5" ry="5" x="110" y="4" width="${width}" height="8" fill="${progressBarBackgroundColor}"></rect>
      <rect
        height="8"
        fill="${color}"
        rx="5" ry="5" x="110" y="4"
        data-testid="lang-progress"
        width="${progressPercentage}%"
      >
      </rect>
    </svg>
  `;
};

const createTextNode = ({
  id,
  label,
  value,
  index,
  percent,
  hideProgress,
  progressBarColor,
  progressBarBackgroundColor,
}) => {
  const staggerDelay = (index + 3) * 150;

  const cardProgress = hideProgress
    ? null
    : createProgressNode({
        progress: percent,
        color: progressBarColor,
        width: 220,
        name: label,
        progressBarBackgroundColor,
      });

  return `
    <g class="stagger" style="animation-delay: ${staggerDelay}ms" transform="translate(25, 0)">
      <text class="stat bold" y="12.5">${label}:</text>
      <text 
        class="stat" 
        x="${hideProgress ? 170 : 350}" 
        y="12.5" 
        data-testid="${id}"
      >${value}</text>
      ${cardProgress}
    </g>
  `;
};

const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
  const { languages } = stats;
  const {
    hide_title = false,
    hide_border = false,
    line_height = 25,
    title_color,
    icon_color,
    text_color,
    bg_color,
    theme = "default",
    hide_progress,
  } = options;

  const lheight = parseInt(line_height, 10);

  // returns theme based colors with proper overrides and defaults
  const { titleColor, textColor, iconColor, bgColor } = getCardColors({
    title_color,
    icon_color,
    text_color,
    bg_color,
    theme,
  });

  const statItems = languages
    ? languages
        .filter((language) => language.hours || language.minutes)
        .map((language) => {
          return createTextNode({
            id: language.name,
            label: language.name,
            value: language.text,
            percent: language.percent,
            progressBarColor: titleColor,
            progressBarBackgroundColor: textColor,
            hideProgress: hide_progress,
          });
        })
    : [];

  // Calculate the card height depending on how many items there are
  // but if rank circle is visible clamp the minimum height to `150`
  let height = Math.max(45 + (statItems.length + 1) * lheight, 150);

  const cssStyles = getStyles({
    titleColor,
    textColor,
    iconColor,
  });

  const card = new Card({
    title: "Wakatime week stats",
    width: 495,
    height,
    colors: {
      titleColor,
      textColor,
      iconColor,
      bgColor,
    },
  });

  card.setHideBorder(hide_border);
  card.setHideTitle(hide_title);
  card.setCSS(
    `
    ${cssStyles}
    .lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
    `
  );

  return card.render(`
    <svg x="0" y="0" width="100%">
      ${FlexLayout({
        items: statItems.length
          ? statItems
          : [noCodingActivityNode({ color: textColor })],
        gap: lheight,
        direction: "column",
      }).join("")}
    </svg> 
  `);
};

module.exports = renderWakatimeCard;
+13 −0
Original line number Diff line number Diff line
const axios = require("axios");

const fetchLast7Days = async ({ username }) => {
  const { data } = await axios.get(
    `https://wakatime.com/api/v1/users/${username}/stats/last_7_days?is_including_today=true`
  );

  return data.data;
};

module.exports = {
  fetchLast7Days,
};
Loading