Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugins/leetcode): add plugin #1213

Merged
merged 3 commits into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ lastname
leaderboard
lecoq
legoandmars
leetcode
LeetCode
libgconf
libssl
libx
Expand Down
28 changes: 28 additions & 0 deletions source/app/web/statics/embed/app.placeholder.js
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,34 @@
},
})
: null),
//LeetCode
...(set.plugins.enabled.leetcode
? ({
leetcode: {
user: options["leetcode.user"],
sections: options["leetcode.sections"].split(",").map(x => x.trim()).filter(x => x),
languages: new Array(6).fill(null).map(_ => ({
language:faker.hacker.noun(),
solved:faker.datatype.number(200)
})),
skills: new Array(Number(options["leetcode.limit.skills"]) || 10).fill(null).map(_ => ({
name:faker.hacker.noun(),
category:faker.helpers.arrayElement(["advanced", "intermediate", "fundamental"]),
solved:faker.datatype.number(30)
})),
problems: {
All: { count: 2402, solved: faker.datatype.number(2402) },
Easy: { count: 592, solved: faker.datatype.number(592) },
Medium: { count: 1283, solved: faker.datatype.number(1283) },
Hard: { count: 527, solved: faker.datatype.number(527) }
},
recent: new Array(Number(options["leetcode.limit.recent"]) || 2).fill(null).map(_ => ({
title:faker.lorem.sentence(),
date:faker.date.recent(),
})),
},
})
: null),
//Activity
...(set.plugins.enabled.activity
? ({
Expand Down
12 changes: 12 additions & 0 deletions source/plugins/leetcode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!--header-->
<!--/header-->

## ➡️ Available options

<!--options-->
<!--/options-->

## ℹ️ Examples workflows

<!--examples-->
<!--/examples-->
8 changes: 8 additions & 0 deletions source/plugins/leetcode/examples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
- name: LeetCode
uses: lowlighter/metrics@latest
with:
filename: metrics.plugin.leetcode.svg
token: NOT_NEEDED
base: ""
plugin_leetcode: yes
plugin_leetcode_sections: solved, skills, recent
54 changes: 54 additions & 0 deletions source/plugins/leetcode/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//Setup
export default async function({login, q, imports, data, queries, account}, {enabled = false, extras = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
if ((!q.leetcode) || (!imports.metadata.plugins.leetcode.enabled(enabled, {extras})))
return null

//Load inputs
let {user, sections, "limit.skills":_limit_skills, "limit.recent":_limit_recent} = imports.metadata.plugins.leetcode.inputs({data, account, q})
const result = {user, sections, languages:[], skills:[], problems:{}, recent:[]}

//Languages stats
{
console.debug(`metrics/compute/${login}/plugins > leetcode > querying api (languages statistics)`)
const {data:{data:{matchedUser:{languageProblemCount:languages}}}} = await imports.axios.post("https://leetcode.com/graphql", {variables: {username: user}, query: queries.leetcode.languages()})
result.languages = languages.map(({languageName:language, problemsSolved:solved}) => ({language, solved}))
}

//Skills stats
{
console.debug(`metrics/compute/${login}/plugins > leetcode > querying api (skills statistics)`)
const {data:{data:{matchedUser:{tagProblemCounts:skills}}}} = await imports.axios.post("https://leetcode.com/graphql", {variables: {username: user}, query: queries.leetcode.skills()})
for (const category in skills)
result.skills.push(...skills[category].map(({tagName:name, problemsSolved:solved}) => ({name, solved, category})))
result.skills.sort((a, b) => b.solved - a.solved)
result.skills = result.skills.slice(0, _limit_skills || Infinity)
}

//Problems
{
console.debug(`metrics/compute/${login}/plugins > leetcode > querying api (problems statistics)`)
const {data:{data:{allQuestionsCount:all, matchedUser:{submitStatsGlobal:{acSubmissionNum:submissions}}}}} = await imports.axios.post("https://leetcode.com/graphql", {variables: {username: user}, query: queries.leetcode.problems()})
for (const {difficulty, count} of all)
result.problems[difficulty] = {count, solved:0}
for (const {difficulty, count:solved} of submissions)
result.problems[difficulty].solved = solved
}

//Recent submissions
{
console.debug(`metrics/compute/${login}/plugins > leetcode > querying api (recent submissions statistics)`)
const {data:{data:{recentAcSubmissionList:submissions}}} = await imports.axios.post("https://leetcode.com/graphql", {variables: {username: user, limit:_limit_recent}, query: queries.leetcode.recent()})
result.recent = submissions.map(({title, timestamp}) => ({title, date:new Date(timestamp*1000)}))
}

//Results
return result
}
//Handle errors
catch (error) {
throw imports.format.error(error)
}
}
59 changes: 59 additions & 0 deletions source/plugins/leetcode/metadata.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: 🗳️ Leetcode
category: social
description: |
This plugin displays statistics from a [LeetCode](https://leetcode.com) account.
disclaimer: |
This plugin is not affiliated, associated, authorized, endorsed by, or in any way officially connected with [LeetCode](https://leetcode.com).
All product and company names are trademarks™ or registered® trademarks of their respective holders.
examples:
default: https://github.com/lowlighter/metrics/blob/examples/metrics.plugin.leetcode.svg
index: 9
supports:
- user
scopes: []
inputs:

plugin_leetcode:
description: |
Enable leetcode plugin
type: boolean
default: no

plugin_leetcode_user:
type: string
description: |
LeetCode login
default: .user.login
preset: no

plugin_leetcode_sections:
description: |
Displayed sections

- `solved` will display solved problems scores
- `skills` will display solved problems tagged skills
- `recent` will display recent submissions
type: array
format: comma-separated
default: solved
example: solved, skills, recent
values:
- solved
- skills
- recent

plugin_leetcode_limit_skills:
description: |
Display limit (skills)
type: number
default: 10
min: 0
zero: disable

plugin_leetcode_limit_recent:
description: |
Display limit (recent)
type: number
default: 2
min: 1
max: 15
8 changes: 8 additions & 0 deletions source/plugins/leetcode/queries/languages.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
query Languages ($username: String!) {
matchedUser(username: $username) {
languageProblemCount {
languageName
problemsSolved
}
}
}
18 changes: 18 additions & 0 deletions source/plugins/leetcode/queries/problems.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
query Problems ($username: String!) {
allQuestionsCount {
difficulty
count
}
matchedUser(username: $username) {
problemsSolvedBeatsStats {
difficulty
percentage
}
submitStatsGlobal {
acSubmissionNum {
difficulty
count
}
}
}
}
8 changes: 8 additions & 0 deletions source/plugins/leetcode/queries/recent.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
query Recent ($username: String!, $limit: Int!) {
recentAcSubmissionList(username: $username, limit: $limit) {
id
title
titleSlug
timestamp
}
}
18 changes: 18 additions & 0 deletions source/plugins/leetcode/queries/skills.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
query Skills ($username: String!) {
matchedUser(username: $username) {
tagProblemCounts {
advanced {
tagName
problemsSolved
}
intermediate {
tagName
problemsSolved
}
fundamental {
tagName
problemsSolved
}
}
}
}
1 change: 1 addition & 0 deletions source/templates/classic/partials/_.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"skyline",
"support",
"stackoverflow",
"leetcode",
"stock",
"achievements",
"screenshot",
Expand Down
68 changes: 68 additions & 0 deletions source/templates/classic/partials/leetcode.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<% if (plugins.leetcode) { %>
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.5 3.5v3h3v-3h-3zM2 2a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V3a1 1 0 00-1-1H2zm4.655 8.595a.75.75 0 010 1.06L4.03 14.28a.75.75 0 01-1.06 0l-1.5-1.5a.75.75 0 111.06-1.06l.97.97 2.095-2.095a.75.75 0 011.06 0zM9.75 2.5a.75.75 0 000 1.5h5.5a.75.75 0 000-1.5h-5.5zm0 5a.75.75 0 000 1.5h5.5a.75.75 0 000-1.5h-5.5zm0 5a.75.75 0 000 1.5h5.5a.75.75 0 000-1.5h-5.5z"></path></svg>
LeetCode statistics <% if (plugins.leetcode?.user) { %>for <%= plugins.leetcode.user %><% } %>
</h2>
<% if (plugins.leetcode.error) { %>
<div class="row">
<section>
<div class="field error">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>
<%= plugins.leetcode.error.message %>
</div>
</section>
</div>
<% } else { %>
<% if (plugins.leetcode.sections.includes("solved")) { %>
<section>
<div class="row fill-width leetcode scores">
<section class="categories">
<% for (const difficulty of ["All", "Easy", "Medium", "Hard"]) { const problems = plugins.leetcode.problems[difficulty], width = 440 * (1 + large) %>
<div class="category column">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="50" height="50" class="gauge <%= difficulty.toLocaleLowerCase() %>">
<circle class="gauge-base" r="53" cx="60" cy="60"></circle>
<circle class="gauge-arc" transform="rotate(-90 60 60)" r="53" cx="60" cy="60" stroke-dasharray="<%= (problems.solved/problems.count) * 329 %> 329"></circle>
<text x="60" y="50" dominant-baseline="central"><%= problems.solved %></text>
<text x="60" y="80" dominant-baseline="central" class="secondary">/<%= problems.count %></text>
</svg>
<span class="title"><%= difficulty %></span>
</div>
<% } %>
</section>
</div>
</section>
<% } %>
<% if (plugins.leetcode.sections.includes("skills")) { %>
<section class="leetcode subsection">
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.5 7.775V2.75a.25.25 0 01.25-.25h5.025a.25.25 0 01.177.073l6.25 6.25a.25.25 0 010 .354l-5.025 5.025a.25.25 0 01-.354 0l-6.25-6.25a.25.25 0 01-.073-.177zm-1.5 0V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 010 2.474l-5.026 5.026a1.75 1.75 0 01-2.474 0l-6.25-6.25A1.75 1.75 0 011 7.775zM6 5a1 1 0 100 2 1 1 0 000-2z"></path></svg>
Skills
</h2>
<div class="topics">
<% for (const {name, solved, category} of plugins.leetcode.skills) { %>
<div class="label <%= category %>"><span class="dot">⬤</span> <%= name %> <span class="count">x<%= solved%></span></div>
<% } %>
</div>
</section>
<% } %>
<% if (plugins.leetcode.sections.includes("recent")) { %>
<section class="leetcode subsection">
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.5 1.75a.25.25 0 01.25-.25h8.5a.25.25 0 01.25.25v7.736a.75.75 0 101.5 0V1.75A1.75 1.75 0 0011.25 0h-8.5A1.75 1.75 0 001 1.75v11.5c0 .966.784 1.75 1.75 1.75h3.17a.75.75 0 000-1.5H2.75a.25.25 0 01-.25-.25V1.75zM4.75 4a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-4.5zM4 7.75A.75.75 0 014.75 7h2a.75.75 0 010 1.5h-2A.75.75 0 014 7.75zm11.774 3.537a.75.75 0 00-1.048-1.074L10.7 14.145 9.281 12.72a.75.75 0 00-1.062 1.058l1.943 1.95a.75.75 0 001.055.008l4.557-4.45z"></path></svg>
Recent submissions
</h2>
<% for (const {title, date} of plugins.leetcode.recent) { %>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 5.5a2.5 2.5 0 100 5 2.5 2.5 0 000-5zM4 8a4 4 0 118 0 4 4 0 01-8 0z"></path></svg>
<div class="infos">
<div class="title"><%= title %></div>
<div class="date"><%= f.date(new Date(date), {date:true, timeZone:config.timezone?.name}) %></div>
</div>
</div>
<% } %>
</section>
<% } %>
<% } %>
</section>
<% } %>
40 changes: 40 additions & 0 deletions source/templates/classic/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,12 @@
text-anchor: middle;
font-weight: 600;
}
.gauge text.secondary {
fill: currentColor;
font-size: 25px;
font-family: monospace;
text-anchor: middle;
}
.gauge .title {
font-size: 18px;
color: #777777;
Expand Down Expand Up @@ -1336,6 +1342,40 @@
overflow: hidden;
}

/* LeetCode */
.leetcode.subsection {
padding-left: 28px;
}
.leetcode .topics {
margin-left: 20px;
}
.leetcode .count {
font-size: 10px;
color: #666666;
}
.leetcode .fundamental .dot, .leetcode .easy.gauge .gauge-arc {
color: #2CBB5D;
}
.leetcode .intermediate .dot, .leetcode .medium.gauge .gauge-arc {
color: #FFC01E;
}
.leetcode .advanced .dot, .leetcode .hard.gauge .gauge-arc {
color: #EF4743;
}
.leetcode .all.gauge .gauge-arc {
color: rgb(255, 161, 22);
}
.leetcode {
align-items: flex-start;
}
.leetcode .infos {
margin-bottom: 3px;
}
.leetcode .infos .date {
font-size: 10px;
color: #666666;
}

/* Code snippet */
.snippet .body {
padding-left: 12px;
Expand Down
Loading