github-heatmap 1.4.1__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/PKG-INFO +1 -1
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/README.md +8 -8
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/cli.py +28 -12
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/config.py +2 -2
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/drawer.py +10 -10
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/base_loader.py +18 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/config.py +2 -2
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/github_loader.py +2 -1
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/gitlab_loader.py +1 -1
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/notion_loader.py +58 -20
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/weread_loader.py +23 -15
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/poster.py +16 -10
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/skyline/skyline.py +1 -1
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/utils.py +4 -1
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap.egg-info/PKG-INFO +1 -1
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/setup.py +1 -1
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/tests/test_notion_loader.py +44 -5
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/tests/test_poster_utils.py +1 -4
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/LICENSE +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/__init__.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/__main__.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/circluar_drawer.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/err.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/html_parser/__init__.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/html_parser/github_parser.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/html_parser/gitlab_parser.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/html_parser/jike_parse.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/html_parser/kindle_parser.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/__init__.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/apple_health_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/bbdc_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/bilibili_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/chatgpt_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/cichang_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/covid_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/dota2_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/duolingo_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/forest_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/from_github_issue_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/garmin_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/gpx_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/jike_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/json_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/kindle_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/leetcode_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/multiple_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/neodb_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/nrc_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/ns_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/openlanguage_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/shanbay_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/strava_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/summary_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/todoist_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/wakatime_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/youtube_loader.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/skyline/__init__.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/skyline/config.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/skyline/font/__init__.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/structures.py +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap.egg-info/SOURCES.txt +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap.egg-info/dependency_links.txt +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap.egg-info/entry_points.txt +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap.egg-info/requires.txt +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap.egg-info/top_level.txt +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/pyproject.toml +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/setup.cfg +0 -0
- {github_heatmap-1.4.1 → github_heatmap-2.2.0}/tests/__init__.py +0 -0
|
@@ -435,18 +435,18 @@ github_heatmap nike --nike_refresh_token="your nike_refresh_token" --year 2012-2
|
|
|
435
435
|
2. 点击「New integration」添加基础信息后,创建新的 Token
|
|
436
436
|
3. 提交后可以看到 `Secrets` 下的 `Internal Integration Token`
|
|
437
437
|
|
|
438
|
-
获取用于生成 Poster 的 Notion
|
|
438
|
+
获取用于生成 Poster 的 Notion 数据源 ID(`data_source_id`),查看[官方文档](https://developers.notion.com/reference/query-a-data-source)获取更多信息。
|
|
439
439
|
|
|
440
|
-
1.
|
|
441
|
-
2.
|
|
442
|
-
3. 其中 `{
|
|
440
|
+
1. 在 Notion 中打开对应的数据源页面
|
|
441
|
+
2. 复制页面链接,链接一般类似 `https://www.notion.so/{workspace_name}/{data_source_id}?v={view_id}`
|
|
442
|
+
3. 其中 `{data_source_id}` 部分即为数据源 ID
|
|
443
443
|
|
|
444
|
-
|
|
444
|
+
注:数据源需要至少包含一个类型为 `Date` 的属性作为日期字段,以及一个数值/公式/rollup 属性作为热力图统计值。生成时通过 `--date_prop_name` 和 `--value_prop_name` 指定字段名。
|
|
445
445
|
|
|
446
446
|
```
|
|
447
|
-
python3 -m github_heatmap notion --notion_token="your notion_token" --
|
|
447
|
+
python3 -m github_heatmap notion --notion_token="your notion_token" --data_source_id="your data_source_id" --date_prop_name="your date_prop_name" --value_prop_name="your value_prop_name"
|
|
448
448
|
or
|
|
449
|
-
github_heatmap notion --notion_token="your notion_token" --
|
|
449
|
+
github_heatmap notion --notion_token="your notion_token" --data_source_id="your data_source_id" --date_prop_name="your date_prop_name" --value_prop_name="your value_prop_name"
|
|
450
450
|
```
|
|
451
451
|
|
|
452
452
|
</details>
|
|
@@ -738,4 +738,4 @@ python3 -m github_heatmap neodb --neodb_token <token> --mark_type <complete, wis
|
|
|
738
738
|
|
|
739
739
|
谢谢就够了
|
|
740
740
|
|
|
741
|
-
Just enjoy it
|
|
741
|
+
Just enjoy it
|
|
@@ -7,22 +7,22 @@ import os
|
|
|
7
7
|
import sys
|
|
8
8
|
|
|
9
9
|
from github_heatmap.circluar_drawer import CircularDrawer
|
|
10
|
-
from github_heatmap.config import TYPE_INFO_DICT
|
|
11
|
-
from github_heatmap.drawer import Drawer
|
|
12
|
-
from github_heatmap.err import DepNotInstalledError
|
|
13
|
-
from github_heatmap.loader import LOADER_DICT
|
|
14
|
-
from github_heatmap.poster import Poster
|
|
15
|
-
from github_heatmap.utils import build_level_colors, parse_years, reduce_year_list
|
|
16
10
|
from github_heatmap.config import (
|
|
17
|
-
GITHUB_LEVEL_COLORS,
|
|
18
|
-
HEAD_FONT_SIZE,
|
|
19
|
-
YEAR_FONT_SIZE,
|
|
20
|
-
MONTH_FONT_SIZE,
|
|
21
11
|
DOM_BOX_PADING,
|
|
22
12
|
DOM_BOX_TUPLE,
|
|
13
|
+
GITHUB_LEVEL_COLORS,
|
|
14
|
+
HEAD_FONT_SIZE,
|
|
15
|
+
MARGIN_LEFT,
|
|
23
16
|
MARGIN_TOP,
|
|
24
|
-
|
|
17
|
+
MONTH_FONT_SIZE,
|
|
18
|
+
TYPE_INFO_DICT,
|
|
19
|
+
YEAR_FONT_SIZE,
|
|
25
20
|
)
|
|
21
|
+
from github_heatmap.drawer import Drawer
|
|
22
|
+
from github_heatmap.err import DepNotInstalledError
|
|
23
|
+
from github_heatmap.loader import LOADER_DICT
|
|
24
|
+
from github_heatmap.poster import Poster
|
|
25
|
+
from github_heatmap.utils import build_level_colors, parse_years, reduce_year_list
|
|
26
26
|
|
|
27
27
|
OUT_FOLDER = os.path.join(os.getcwd(), "OUT_FOLDER")
|
|
28
28
|
|
|
@@ -76,6 +76,7 @@ def run():
|
|
|
76
76
|
}
|
|
77
77
|
p.level_colors = level_colors
|
|
78
78
|
p.use_github_level_mapping = not (args.special_number1 or args.special_number2)
|
|
79
|
+
p.use_raw_level = args.use_raw_level
|
|
79
80
|
|
|
80
81
|
p.tooltip_template = args.tooltip_template or None
|
|
81
82
|
|
|
@@ -142,6 +143,16 @@ def run():
|
|
|
142
143
|
for child_loader in loader.loader_list
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
# explicit level thresholds override quartile calculation
|
|
147
|
+
if args.level_thresholds:
|
|
148
|
+
p.level_thresholds = tuple(float(v) for v in args.level_thresholds.split(","))
|
|
149
|
+
p.use_github_level_mapping = True
|
|
150
|
+
|
|
151
|
+
# explicit level colors override computed colors
|
|
152
|
+
if args.level_colors:
|
|
153
|
+
p.level_colors = [c.strip() for c in args.level_colors.split(",")]
|
|
154
|
+
p.use_github_level_mapping = True
|
|
155
|
+
|
|
145
156
|
# set title
|
|
146
157
|
# we don't know issue content so use name
|
|
147
158
|
p.title = (
|
|
@@ -166,7 +177,12 @@ def run():
|
|
|
166
177
|
MARGIN_TOP
|
|
167
178
|
+ HEAD_FONT_SIZE
|
|
168
179
|
+ poster_length
|
|
169
|
-
* (
|
|
180
|
+
* (
|
|
181
|
+
YEAR_FONT_SIZE
|
|
182
|
+
+ MONTH_FONT_SIZE
|
|
183
|
+
+ DOM_BOX_PADING * 3
|
|
184
|
+
+ (DOM_BOX_PADING + DOM_BOX_TUPLE[0]) * 7
|
|
185
|
+
)
|
|
170
186
|
)
|
|
171
187
|
if not os.path.exists(OUT_FOLDER):
|
|
172
188
|
os.mkdir(OUT_FOLDER)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
DOM_BOX_TUPLE = (4,4)
|
|
1
|
+
DOM_BOX_TUPLE = (4, 4)
|
|
2
2
|
DOM_BOX_TUPLE_LIST_FOR_TWO = ((2.6, 1.3), (2.6, 1.3))
|
|
3
3
|
DOM_BOX_TUPLE_LIST_FOR_THREE = ((2.7, 0.9), (2.7, 0.9), (2.7, 0.9))
|
|
4
4
|
|
|
@@ -11,7 +11,7 @@ DOM_BOX_DICT = {
|
|
|
11
11
|
3: {"dom": DOM_BOX_TUPLE_LIST_FOR_THREE},
|
|
12
12
|
}
|
|
13
13
|
MARGIN_LEFT = 10
|
|
14
|
-
MARGIN_TOP
|
|
14
|
+
MARGIN_TOP = 4
|
|
15
15
|
HEAD_FONT_SIZE = 6
|
|
16
16
|
YEAR_FONT_SIZE = 3
|
|
17
17
|
MONTH_FONT_SIZE = 2.5
|
|
@@ -7,19 +7,15 @@ from github_heatmap.config import (
|
|
|
7
7
|
COLOR_TUPLE,
|
|
8
8
|
DEFAULT_DOM_COLOR,
|
|
9
9
|
DOM_BOX_DICT,
|
|
10
|
+
DOM_BOX_PADING,
|
|
11
|
+
DOM_BOX_RADIUS,
|
|
10
12
|
DOM_BOX_TUPLE,
|
|
13
|
+
MONTH_FONT_SIZE,
|
|
11
14
|
MONTH_NAMES,
|
|
12
15
|
YEAR_FONT_SIZE,
|
|
13
|
-
MONTH_FONT_SIZE,
|
|
14
|
-
DOM_BOX_PADING,
|
|
15
|
-
DOM_BOX_RADIUS,
|
|
16
16
|
)
|
|
17
17
|
from github_heatmap.err import BaseDrawError
|
|
18
|
-
from github_heatmap.utils import
|
|
19
|
-
interpolate_color,
|
|
20
|
-
make_key_times,
|
|
21
|
-
resolve_github_level,
|
|
22
|
-
)
|
|
18
|
+
from github_heatmap.utils import interpolate_color, make_key_times, resolve_github_level
|
|
23
19
|
|
|
24
20
|
|
|
25
21
|
class Drawer:
|
|
@@ -81,7 +77,7 @@ class Drawer:
|
|
|
81
77
|
return self.poster.level_thresholds
|
|
82
78
|
|
|
83
79
|
def _make_github_level_color(self, length, type_name=None):
|
|
84
|
-
level = resolve_github_level(length, self._resolve_level_thresholds(type_name))
|
|
80
|
+
level = resolve_github_level(length, self._resolve_level_thresholds(type_name), self.poster.use_raw_level)
|
|
85
81
|
if level == 0:
|
|
86
82
|
return self.poster.colors.get("dom")
|
|
87
83
|
return self.poster.level_colors[level - 1]
|
|
@@ -269,7 +265,11 @@ class Drawer:
|
|
|
269
265
|
style=self.month_names_style,
|
|
270
266
|
)
|
|
271
267
|
)
|
|
272
|
-
if
|
|
268
|
+
if (
|
|
269
|
+
index > 0
|
|
270
|
+
and index < 53
|
|
271
|
+
and month != MONTH_NAMES[github_rect_day.month - 1]
|
|
272
|
+
):
|
|
273
273
|
month = MONTH_NAMES[github_rect_day.month - 1]
|
|
274
274
|
dr.add(
|
|
275
275
|
dr.text(
|
|
@@ -233,6 +233,24 @@ class BaseLoader(ABC):
|
|
|
233
233
|
action="store_true",
|
|
234
234
|
help="if account is CN",
|
|
235
235
|
)
|
|
236
|
+
group.add_argument(
|
|
237
|
+
"--use-raw-level",
|
|
238
|
+
dest="use_raw_level",
|
|
239
|
+
action="store_true",
|
|
240
|
+
help="Use raw value (1-4) as level directly, skip quartile calculation",
|
|
241
|
+
)
|
|
242
|
+
group.add_argument(
|
|
243
|
+
"--level-thresholds",
|
|
244
|
+
dest="level_thresholds",
|
|
245
|
+
default="",
|
|
246
|
+
help="Explicit level thresholds (comma-separated, e.g. '360,450,99999'), overrides quartile calculation",
|
|
247
|
+
)
|
|
248
|
+
group.add_argument(
|
|
249
|
+
"--level-colors",
|
|
250
|
+
dest="level_colors",
|
|
251
|
+
default="",
|
|
252
|
+
help="Explicit level colors (comma-separated hex, e.g. '#E74C3C,#F1C40F,#2ECC71,#2ECC71'), overrides computed colors",
|
|
253
|
+
)
|
|
236
254
|
# special here
|
|
237
255
|
group.add_argument(
|
|
238
256
|
"--stand-with-ukraine",
|
|
@@ -115,8 +115,8 @@ JIKE_PERSON_URL = "https://web.okjike.com/u/{user_id}"
|
|
|
115
115
|
BBDC_API_URL = "https://learnywhere.cn/bb/dashboard/profile/search?userId={user_id}"
|
|
116
116
|
|
|
117
117
|
# Notion
|
|
118
|
-
NOTION_API_URL = "https://api.notion.com/v1/
|
|
119
|
-
NOTION_API_VERSION = "
|
|
118
|
+
NOTION_API_URL = "https://api.notion.com/v1/data_sources/{data_source_id}/query"
|
|
119
|
+
NOTION_API_VERSION = "2026-03-11"
|
|
120
120
|
|
|
121
121
|
# Weread
|
|
122
122
|
WEREAD_BASE_URL = "https://weread.qq.com/"
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
2
3
|
import time
|
|
4
|
+
import warnings
|
|
3
5
|
from collections import defaultdict
|
|
6
|
+
from datetime import datetime, timedelta
|
|
4
7
|
|
|
5
8
|
import pendulum
|
|
6
9
|
import requests
|
|
7
|
-
import json
|
|
8
10
|
|
|
9
11
|
from github_heatmap.loader.base_loader import BaseLoader
|
|
10
12
|
from github_heatmap.loader.config import NOTION_API_URL, NOTION_API_VERSION
|
|
@@ -17,12 +19,28 @@ class NotionLoader(BaseLoader):
|
|
|
17
19
|
def __init__(self, from_year, to_year, _type, **kwargs):
|
|
18
20
|
super().__init__(from_year, to_year, _type)
|
|
19
21
|
self.number_by_date_dict = self.generate_date_dict(from_year, to_year)
|
|
20
|
-
self.notion_token = kwargs.get("notion_token"
|
|
21
|
-
self.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
self.notion_token = (kwargs.get("notion_token") or "").strip()
|
|
23
|
+
self.data_source_id = (kwargs.get("data_source_id") or "").strip()
|
|
24
|
+
deprecated_database_id = (kwargs.get("database_id") or "").strip()
|
|
25
|
+
if deprecated_database_id and not self.data_source_id:
|
|
26
|
+
warnings.warn(
|
|
27
|
+
"--database_id is deprecated; use --data_source_id instead.",
|
|
28
|
+
DeprecationWarning,
|
|
29
|
+
stacklevel=2,
|
|
30
|
+
)
|
|
31
|
+
self.data_source_id = deprecated_database_id
|
|
32
|
+
self.date_prop_name = kwargs.get("date_prop_name") or ""
|
|
33
|
+
self.value_prop_name = kwargs.get("value_prop_name") or ""
|
|
34
|
+
self.data_source_filter = kwargs.get("data_source_filter") or ""
|
|
35
|
+
deprecated_database_filter = kwargs.get("database_filter") or ""
|
|
36
|
+
if deprecated_database_filter and not self.data_source_filter:
|
|
37
|
+
warnings.warn(
|
|
38
|
+
"--database_filter is deprecated; use --data_source_filter instead.",
|
|
39
|
+
DeprecationWarning,
|
|
40
|
+
stacklevel=2,
|
|
41
|
+
)
|
|
42
|
+
self.data_source_filter = deprecated_database_filter
|
|
43
|
+
self.tooltip_prop_name = (kwargs.get("tooltip_prop_name") or "").strip()
|
|
26
44
|
self.tooltip_by_date_dict = defaultdict(list)
|
|
27
45
|
|
|
28
46
|
@classmethod
|
|
@@ -33,11 +51,17 @@ class NotionLoader(BaseLoader):
|
|
|
33
51
|
type=str,
|
|
34
52
|
help="The Notion internal integration token.",
|
|
35
53
|
)
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--data_source_id",
|
|
56
|
+
dest="data_source_id",
|
|
57
|
+
type=str,
|
|
58
|
+
help="The Notion data source id.",
|
|
59
|
+
)
|
|
36
60
|
parser.add_argument(
|
|
37
61
|
"--database_id",
|
|
38
62
|
dest="database_id",
|
|
39
63
|
type=str,
|
|
40
|
-
help=
|
|
64
|
+
help=argparse.SUPPRESS,
|
|
41
65
|
)
|
|
42
66
|
parser.add_argument(
|
|
43
67
|
"--date_prop_name",
|
|
@@ -45,7 +69,7 @@ class NotionLoader(BaseLoader):
|
|
|
45
69
|
type=str,
|
|
46
70
|
default="Datetime",
|
|
47
71
|
required=optional,
|
|
48
|
-
help="The
|
|
72
|
+
help="The Notion data source property name that stores the date.",
|
|
49
73
|
)
|
|
50
74
|
parser.add_argument(
|
|
51
75
|
"--value_prop_name",
|
|
@@ -53,15 +77,23 @@ class NotionLoader(BaseLoader):
|
|
|
53
77
|
type=str,
|
|
54
78
|
default="Datetime",
|
|
55
79
|
required=optional,
|
|
56
|
-
help="The
|
|
57
|
-
)
|
|
80
|
+
help="The Notion data source property name used as the heatmap value.",
|
|
81
|
+
)
|
|
82
|
+
parser.add_argument(
|
|
83
|
+
"--data_source_filter",
|
|
84
|
+
dest="data_source_filter",
|
|
85
|
+
type=str,
|
|
86
|
+
default="",
|
|
87
|
+
required=False,
|
|
88
|
+
help="Optional Notion data source query filter in JSON format.",
|
|
89
|
+
)
|
|
58
90
|
parser.add_argument(
|
|
59
91
|
"--database_filter",
|
|
60
92
|
dest="database_filter",
|
|
61
93
|
type=str,
|
|
62
94
|
default="",
|
|
63
95
|
required=False,
|
|
64
|
-
help=
|
|
96
|
+
help=argparse.SUPPRESS,
|
|
65
97
|
)
|
|
66
98
|
parser.add_argument(
|
|
67
99
|
"--tooltip_prop_name",
|
|
@@ -90,8 +122,8 @@ class NotionLoader(BaseLoader):
|
|
|
90
122
|
]
|
|
91
123
|
},
|
|
92
124
|
}
|
|
93
|
-
if self.
|
|
94
|
-
payload["filter"]["and"].append(json.loads(self.
|
|
125
|
+
if self.data_source_filter:
|
|
126
|
+
payload["filter"]["and"].append(json.loads(self.data_source_filter))
|
|
95
127
|
if start_cursor:
|
|
96
128
|
payload.update({"start_cursor": start_cursor})
|
|
97
129
|
print(payload)
|
|
@@ -104,14 +136,14 @@ class NotionLoader(BaseLoader):
|
|
|
104
136
|
print(headers)
|
|
105
137
|
try:
|
|
106
138
|
resp = requests.post(
|
|
107
|
-
NOTION_API_URL.format(
|
|
139
|
+
NOTION_API_URL.format(data_source_id=self.data_source_id),
|
|
108
140
|
json=payload,
|
|
109
141
|
headers=headers,
|
|
110
142
|
)
|
|
111
143
|
except requests.RequestException:
|
|
112
144
|
print("Failed to connect to Notion API.")
|
|
113
145
|
return data_list
|
|
114
|
-
print(resp
|
|
146
|
+
print(getattr(resp, "text", ""))
|
|
115
147
|
if not resp.ok:
|
|
116
148
|
# Treat non-OK responses as an empty result set so we can still draw
|
|
117
149
|
# a heatmap even when the Notion API yields no rows for the period.
|
|
@@ -211,7 +243,9 @@ class NotionLoader(BaseLoader):
|
|
|
211
243
|
[p.get("name") or p.get("id", "") for p in item.get("people", [])]
|
|
212
244
|
).strip()
|
|
213
245
|
if item_type == "relation":
|
|
214
|
-
return ", ".join(
|
|
246
|
+
return ", ".join(
|
|
247
|
+
[r.get("id", "") for r in item.get("relation", [])]
|
|
248
|
+
).strip()
|
|
215
249
|
if item_type == "date":
|
|
216
250
|
return cls._format_date(item.get("date", {}))
|
|
217
251
|
if item_type in ("url", "email", "phone_number"):
|
|
@@ -274,7 +308,9 @@ class NotionLoader(BaseLoader):
|
|
|
274
308
|
if prop_type == "date":
|
|
275
309
|
return cls._format_date(prop.get("date", {}))
|
|
276
310
|
if prop_type == "relation":
|
|
277
|
-
return ", ".join(
|
|
311
|
+
return ", ".join(
|
|
312
|
+
[r.get("id", "") for r in prop.get("relation", [])]
|
|
313
|
+
).strip()
|
|
278
314
|
if prop_type == "files":
|
|
279
315
|
texts = []
|
|
280
316
|
for file_obj in prop.get("files", []):
|
|
@@ -310,7 +346,9 @@ class NotionLoader(BaseLoader):
|
|
|
310
346
|
if rollup_type == "date":
|
|
311
347
|
return cls._format_date(rollup.get("date", {}))
|
|
312
348
|
if rollup_type == "array":
|
|
313
|
-
texts = [
|
|
349
|
+
texts = [
|
|
350
|
+
cls._extract_rollup_item(item) for item in rollup.get("array", [])
|
|
351
|
+
]
|
|
314
352
|
texts = [text for text in texts if text]
|
|
315
353
|
return ", ".join(texts)
|
|
316
354
|
return ""
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import os
|
|
3
|
+
|
|
2
4
|
import pendulum
|
|
3
5
|
import requests
|
|
4
|
-
import os
|
|
5
6
|
|
|
6
7
|
from github_heatmap.loader.base_loader import BaseLoader
|
|
7
8
|
from github_heatmap.loader.config import WEREAD_BASE_URL, WEREAD_HISTORY_URL
|
|
8
9
|
|
|
9
10
|
headers = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
"User-Agent": "WeRead/8.2.5 WRBrand/xiaomi Dalvik/2.1.0 (Linux; U; Android 12; Redmi Note 7 Pro Build/SQ3A.220705.004)",
|
|
12
|
+
"Connection": "Keep-Alive",
|
|
13
|
+
"Accept-Encoding": "gzip",
|
|
14
|
+
"baseapi": "32",
|
|
15
|
+
"appver": "8.2.5.10163885",
|
|
16
|
+
"osver": "12",
|
|
17
|
+
"channelId": "11",
|
|
18
|
+
"basever": "8.2.5.10163885",
|
|
19
|
+
"Content-Type": "application/json; charset=UTF-8",
|
|
19
20
|
}
|
|
21
|
+
|
|
22
|
+
|
|
20
23
|
class WereadLoader(BaseLoader):
|
|
21
24
|
track_color = "#2EA8F7"
|
|
22
25
|
unit = "mins"
|
|
@@ -45,7 +48,11 @@ class WereadLoader(BaseLoader):
|
|
|
45
48
|
# )
|
|
46
49
|
|
|
47
50
|
def refresh_token(self):
|
|
48
|
-
body = {
|
|
51
|
+
body = {
|
|
52
|
+
"deviceId": self.device_id,
|
|
53
|
+
"refreshToken": self.token,
|
|
54
|
+
"activationCode": self.activation_code,
|
|
55
|
+
}
|
|
49
56
|
r = self.session.post(
|
|
50
57
|
"https://api.notionhub.app/refresh-weread-token", json=body
|
|
51
58
|
)
|
|
@@ -54,13 +61,14 @@ class WereadLoader(BaseLoader):
|
|
|
54
61
|
vid = response_data.get("vid")
|
|
55
62
|
accessToken = response_data.get("accessToken")
|
|
56
63
|
if vid and accessToken:
|
|
57
|
-
self.session.headers.update(
|
|
64
|
+
self.session.headers.update(
|
|
65
|
+
{"vid": str(vid), "accessToken": accessToken}
|
|
66
|
+
)
|
|
58
67
|
else:
|
|
59
68
|
print("Failed to refresh token")
|
|
60
69
|
else:
|
|
61
70
|
print("Failed to refresh token")
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
|
|
64
72
|
def get_api_data(self):
|
|
65
73
|
r = self.session.get(WEREAD_HISTORY_URL)
|
|
66
74
|
if not r.ok:
|
|
@@ -75,7 +83,7 @@ class WereadLoader(BaseLoader):
|
|
|
75
83
|
|
|
76
84
|
def make_track_dict(self):
|
|
77
85
|
api_data = self.get_api_data()
|
|
78
|
-
if
|
|
86
|
+
if "readTimes" in api_data:
|
|
79
87
|
readTimes = dict(sorted(api_data["readTimes"].items(), reverse=True))
|
|
80
88
|
for k, v in readTimes.items():
|
|
81
89
|
k = pendulum.from_timestamp(int(k), tz=self.time_zone)
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
"""Create a poster from track data."""
|
|
2
|
+
|
|
2
3
|
from collections import defaultdict
|
|
3
4
|
|
|
4
5
|
import svgwrite
|
|
5
6
|
|
|
7
|
+
from github_heatmap.config import HEAD_FONT_SIZE, MARGIN_LEFT, MARGIN_TOP
|
|
6
8
|
from github_heatmap.structures import XY, ValueRange
|
|
7
|
-
|
|
8
|
-
HEAD_FONT_SIZE,
|
|
9
|
-
MARGIN_LEFT,
|
|
10
|
-
MARGIN_TOP
|
|
11
|
-
)
|
|
9
|
+
|
|
12
10
|
|
|
13
11
|
class Poster:
|
|
14
12
|
def __init__(self):
|
|
@@ -28,7 +26,7 @@ class Poster:
|
|
|
28
26
|
self.width = 400
|
|
29
27
|
self.height = 300
|
|
30
28
|
self.years = None
|
|
31
|
-
self.offset = XY(MARGIN_LEFT,MARGIN_TOP)
|
|
29
|
+
self.offset = XY(MARGIN_LEFT, MARGIN_TOP)
|
|
32
30
|
# maybe support more type
|
|
33
31
|
self.tracks_drawer = None
|
|
34
32
|
self.trans = None
|
|
@@ -40,6 +38,7 @@ class Poster:
|
|
|
40
38
|
self.level_thresholds = ()
|
|
41
39
|
self.level_thresholds_by_type = {}
|
|
42
40
|
self.use_github_level_mapping = True
|
|
41
|
+
self.use_raw_level = False
|
|
43
42
|
|
|
44
43
|
# for year summary
|
|
45
44
|
self.is_summary = False
|
|
@@ -107,10 +106,17 @@ class Poster:
|
|
|
107
106
|
def __draw_header(self, d):
|
|
108
107
|
self.offset.y += HEAD_FONT_SIZE
|
|
109
108
|
text_color = self.colors["text"]
|
|
110
|
-
title_style =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
title_style = (
|
|
110
|
+
f"font-size:{HEAD_FONT_SIZE}px; font-family:Arial; font-weight:bold;"
|
|
111
|
+
)
|
|
112
|
+
d.add(
|
|
113
|
+
d.text(
|
|
114
|
+
self.title,
|
|
115
|
+
insert=(self.offset.x, self.offset.y),
|
|
116
|
+
fill=text_color,
|
|
117
|
+
style=title_style,
|
|
118
|
+
)
|
|
119
|
+
)
|
|
114
120
|
|
|
115
121
|
def __draw_footer(self, d):
|
|
116
122
|
self.tracks_drawer.draw_footer(d)
|
|
@@ -93,9 +93,12 @@ def make_github_level_thresholds(number_list):
|
|
|
93
93
|
return tuple(nearest_rank(p) for p in (0.25, 0.50, 0.75))
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
def resolve_github_level(value, thresholds):
|
|
96
|
+
def resolve_github_level(value, thresholds, use_raw_level=False):
|
|
97
97
|
if value <= 0:
|
|
98
98
|
return 0
|
|
99
|
+
if use_raw_level:
|
|
100
|
+
# 直接使用传入的值作为级别(1-4),跳过四分位数计算
|
|
101
|
+
return max(1, min(4, int(value)))
|
|
99
102
|
if not thresholds:
|
|
100
103
|
return 1
|
|
101
104
|
|
|
@@ -6,7 +6,7 @@ setup(
|
|
|
6
6
|
author_email="linkang.ma@gmail.com",
|
|
7
7
|
url="https://github.com/malinkang/GitHubPoster",
|
|
8
8
|
license="MIT",
|
|
9
|
-
version="
|
|
9
|
+
version="2.2.0",
|
|
10
10
|
description="Make everything a GitHub svg poster and Skyline!",
|
|
11
11
|
packages=find_packages(),
|
|
12
12
|
include_package_data=True,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import pytest
|
|
1
2
|
import requests
|
|
2
3
|
|
|
3
4
|
from github_heatmap.loader import notion_loader
|
|
@@ -10,7 +11,7 @@ def build_loader():
|
|
|
10
11
|
2024,
|
|
11
12
|
"notion",
|
|
12
13
|
notion_token="token",
|
|
13
|
-
|
|
14
|
+
data_source_id="ds",
|
|
14
15
|
date_prop_name="Date",
|
|
15
16
|
value_prop_name="Value",
|
|
16
17
|
tooltip_prop_name="Tooltip",
|
|
@@ -46,10 +47,7 @@ def test_notion_loader_make_track_dict_with_tooltips():
|
|
|
46
47
|
loader.make_track_dict()
|
|
47
48
|
|
|
48
49
|
assert loader.number_by_date_dict["2024-01-01"] == 7
|
|
49
|
-
assert
|
|
50
|
-
loader.tooltip_by_date_dict["2024-01-01"]
|
|
51
|
-
== "Study\nRead, Write"
|
|
52
|
-
)
|
|
50
|
+
assert loader.tooltip_by_date_dict["2024-01-01"] == "Study\nRead, Write"
|
|
53
51
|
|
|
54
52
|
|
|
55
53
|
def test_notion_loader_extract_property_text_variants():
|
|
@@ -133,3 +131,44 @@ def test_notion_loader_handles_request_exception(monkeypatch):
|
|
|
133
131
|
assert tracks
|
|
134
132
|
assert all(value == 0 for value in tracks.values())
|
|
135
133
|
assert loader.number_list
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_notion_loader_queries_data_source_endpoint(monkeypatch):
|
|
137
|
+
loader = build_loader()
|
|
138
|
+
captured = {}
|
|
139
|
+
|
|
140
|
+
class DummyResp:
|
|
141
|
+
ok = True
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def json():
|
|
145
|
+
return {"results": [], "next_cursor": None, "has_more": False}
|
|
146
|
+
|
|
147
|
+
def fake_post(url, json, headers):
|
|
148
|
+
captured["url"] = url
|
|
149
|
+
captured["json"] = json
|
|
150
|
+
captured["headers"] = headers
|
|
151
|
+
return DummyResp()
|
|
152
|
+
|
|
153
|
+
monkeypatch.setattr(notion_loader.requests, "post", fake_post)
|
|
154
|
+
|
|
155
|
+
loader.get_api_data()
|
|
156
|
+
|
|
157
|
+
assert captured["url"].endswith("/v1/data_sources/ds/query")
|
|
158
|
+
assert captured["headers"]["Notion-Version"] == "2026-03-11"
|
|
159
|
+
assert captured["json"]["filter"]["and"][0]["property"] == "Date"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_notion_loader_accepts_deprecated_database_id():
|
|
163
|
+
with pytest.warns(DeprecationWarning, match="--database_id is deprecated"):
|
|
164
|
+
loader = NotionLoader(
|
|
165
|
+
2024,
|
|
166
|
+
2024,
|
|
167
|
+
"notion",
|
|
168
|
+
notion_token="token",
|
|
169
|
+
database_id="legacy-db",
|
|
170
|
+
date_prop_name="Date",
|
|
171
|
+
value_prop_name="Value",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
assert loader.data_source_id == "legacy-db"
|
|
@@ -67,10 +67,7 @@ def test_drawer_tooltip_formatting():
|
|
|
67
67
|
assert drawer._format_tooltip("2024-01-01") == "2024-01-01"
|
|
68
68
|
|
|
69
69
|
poster.tooltip_template = "{date} {value} for {type}"
|
|
70
|
-
assert (
|
|
71
|
-
drawer._format_tooltip("2024-01-01", 7, "run")
|
|
72
|
-
== "2024-01-01 7 for run"
|
|
73
|
-
)
|
|
70
|
+
assert drawer._format_tooltip("2024-01-01", 7, "run") == "2024-01-01 7 for run"
|
|
74
71
|
|
|
75
72
|
poster.tooltip_by_date = {"2024-01-01": "Custom"}
|
|
76
73
|
assert drawer._format_tooltip("2024-01-01", 5) == "Custom"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_heatmap-1.4.1 → github_heatmap-2.2.0}/github_heatmap/loader/from_github_issue_loader.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|