Azaya89 commited on
Commit
b5dd7f4
·
1 Parent(s): b43b5a5

re-organize main script

Browse files
Files changed (1) hide show
  1. app.py +139 -58
app.py CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  import pandas as pd
2
  import hvplot.pandas # noqa
3
  import panel as pn
@@ -8,22 +11,15 @@ import panel_material_ui as pmu
8
  # patch_all()
9
  pn.extension("tabulator", autoreload=True)
10
 
11
- MAINTAINERS = {
12
- "HoloViews": ["hoxbro", "philippjfr", "jlstevens"],
13
- "hvPlot": ["maximlt", "philippjfr", "hoxbro"],
14
- "Panel": ["philippjfr", "ahaung11", "maximlt", "hoxbro"],
15
- }
16
-
17
- status_filter = pmu.RadioButtonGroup(
18
- label="Issue Status",
19
- options=["Open Issues", "Closed Issues", "All Issues"],
20
- value="All Issues",
21
- size="small",
22
- button_type="success",
23
- )
24
-
25
 
26
- # Data loading
 
 
27
  data_url = (
28
  "https://raw.githubusercontent.com/Azaya89/holoviz-insights/refs/heads/main/data/"
29
  )
@@ -51,6 +47,40 @@ release_dfs = {
51
  }
52
 
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  def create_release_plot(df, repo_name):
55
  from packaging.version import parse
56
 
@@ -78,6 +108,8 @@ def create_release_plot(df, repo_name):
78
  # Set "x1" to now for the last release
79
  if not df.empty:
80
  df.loc[df.index[-1], "x1"] = pd.Timestamp.now(tz=df["published_at"].dt.tz)
 
 
81
  df["y0"] = df["y"].cat.codes - 0.4
82
  df["y1"] = df["y"].cat.codes + 0.4
83
  last_release = df.iloc[-1]
@@ -86,9 +118,21 @@ def create_release_plot(df, repo_name):
86
  message = f"🔔 Last release was {days_since} days ago on {last_release['published_at'].date()} ({last_release['tag']})"
87
 
88
  rects = hv.Rectangles(
89
- df[["x0", "y0", "x1", "y1", "tag", "type", "published_at", "minor_version"]],
 
 
 
 
 
 
 
 
 
 
 
 
90
  kdims=["x0", "y0", "x1", "y1"],
91
- vdims=["tag", "type", "published_at", "minor_version"],
92
  )
93
  rects = rects.opts(
94
  color="type",
@@ -99,9 +143,9 @@ def create_release_plot(df, repo_name):
99
  tools=["ycrosshair"],
100
  hover_tooltips=[
101
  ("Release Version", "@tag"),
102
- ("Minor Version", "@minor_version"),
103
  ("Release Type", "@type"),
104
  ("Release Date", "@published_at"),
 
105
  ],
106
  xlabel="Date",
107
  ylabel="Minor Version",
@@ -117,27 +161,6 @@ def create_release_plot(df, repo_name):
117
  )
118
 
119
 
120
- # Helper functions
121
- def compute_metrics(df):
122
- metrics = {}
123
- metrics["first_month"] = df.index[-1].strftime("%B %Y")
124
- metrics["last_month"] = df.index[0].strftime("%B %Y")
125
- metrics["total_issues"] = len(df)
126
- metrics["still_open"] = len(df[df["time_to_close"].isna()])
127
- metrics["closed"] = len(df[df["time_to_close"].notna()])
128
- metrics["avg_close_time"] = int(df["time_to_close"].mean().days)
129
- metrics["median_close_time"] = int(df["time_to_close"].median().days)
130
- return metrics
131
-
132
-
133
- def format_issue_url(url):
134
- try:
135
- return f'<a href="{url}" target="_blank">{url.split("/")[-1]}</a>'
136
- except Exception:
137
- return url
138
-
139
-
140
- # Plots
141
  def create_comparison_plot(df):
142
  monthly_opened = df.resample("ME").size()
143
  monthly_closed = df.dropna(subset=["time_to_close"]).resample("ME").size()
@@ -151,7 +174,7 @@ def create_comparison_plot(df):
151
 
152
 
153
  def create_issues_plot(df):
154
- # Calculate the number of open issues for each day (date only)
155
  df = df.copy()
156
  df["opened_date"] = df.index.normalize()
157
  df["closed_date"] = df["opened_date"] + df["time_to_close"]
@@ -174,9 +197,10 @@ def create_issues_plot(df):
174
 
175
 
176
  def create_milestone_plot(df):
177
- # Filter to only include open issues (where time_to_close is null)
178
  df = df[df["time_to_close"].isna()]
179
  milestone_counts = df["milestone"].value_counts(dropna=False)
 
180
  return milestone_counts.hvplot.bar(
181
  title="Open Issues by Milestone",
182
  xlabel="Milestone",
@@ -206,28 +230,51 @@ def create_releases_per_year_plot(release_df):
206
  release_df = release_df.copy()
207
  release_df["year"] = release_df["published_at"].dt.year
208
  releases_per_year = release_df.groupby("year").size()
 
209
  return releases_per_year.hvplot.bar(
210
  xlabel="Year",
211
  ylabel="Number of Releases",
212
  title="Releases per Year",
213
- hover_tooltips=[("Year", "@year"), ("Count", "@0")],
214
  height=300,
215
  width=600,
216
  )
217
 
218
 
 
 
 
219
  styles = {
220
  "box-shadow": "rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px",
221
  "border-radius": "5px",
222
  "padding": "10px",
223
  }
224
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
 
 
 
226
  @pn.depends(repo_selector)
227
  def indicators_view(repo):
228
  df = repo_dfs[repo]
229
  metrics = compute_metrics(df)
230
- return pn.FlexBox(
231
  pn.indicators.Number(
232
  value=metrics["total_issues"],
233
  name="Total Issues Opened",
@@ -258,7 +305,17 @@ def indicators_view(repo):
258
  default_color="blue",
259
  styles=styles,
260
  ),
261
- )
 
 
 
 
 
 
 
 
 
 
262
 
263
 
264
  # State variable to store the active tab index
@@ -289,27 +346,49 @@ def plots_view(repo):
289
  return tabs
290
 
291
 
292
- @pn.depends(repo_selector, status_filter)
293
- def table_view(repo, status):
294
  df = repo_dfs[repo].copy()
295
  if status == "Open Issues":
296
  df = df[df["time_to_close"].isna()]
297
  elif status == "Closed Issues":
298
  df = df[df["time_to_close"].notna()]
 
 
 
 
 
 
 
299
  df["issue_no"] = df["html_url"].apply(format_issue_url)
300
  for col in ["time_to_first_response", "time_to_close"]:
301
  df[f"{col}_str"] = df[col].astype(str)
302
- return pn.widgets.Tabulator(
303
- df,
304
- sizing_mode="stretch_width",
305
- name="Table",
306
- hidden_columns=[
 
307
  "html_url",
308
  "time_to_answer",
309
  "time_in_draft",
310
  "time_to_first_response",
311
  "time_to_close",
312
- ],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  pagination="remote",
314
  page_size=5,
315
  formatters={"issue_no": "html"},
@@ -320,23 +399,23 @@ def table_view(repo, status):
320
  def header_text(repo):
321
  df = repo_dfs[repo]
322
  metrics = compute_metrics(df)
 
323
  text = f"""
324
  ## {repo} Dashboard
325
- ### Issue Metrics from {metrics["first_month"]} to {metrics["last_month"]}
326
  """
327
  return text
328
 
329
 
330
- note = """The issue metrics shown here are not a full historical record, but represent a snapshot collected automatically at the start of each month.
331
- Data covers issues from the stated start date up to the end of the previous month, and is refreshed at the beginning of every new month."""
 
 
332
  icon = pn.widgets.TooltipIcon(value=note)
333
  logo = "https://holoviz.org/_static/holoviz-logo.svg"
334
 
335
  logo_pane = pn.pane.Image(logo, width=200, align="center", margin=(10, 0, 10, 0))
336
 
337
- HEADER_COLOR = "#4199DA"
338
- PAPER_COLOR = "#f5f4ef"
339
-
340
  page = pmu.Page(
341
  main=[
342
  header_text,
@@ -352,6 +431,8 @@ page = pmu.Page(
352
  repo_selector,
353
  "## Filter by Issue Status",
354
  status_filter,
 
 
355
  ],
356
  title="HoloViz Issue Metrics Dashboard",
357
  theme_config={
 
1
+ # =============================
2
+ # Imports & Extensions
3
+ # =============================
4
  import pandas as pd
5
  import hvplot.pandas # noqa
6
  import panel as pn
 
11
  # patch_all()
12
  pn.extension("tabulator", autoreload=True)
13
 
14
+ # =============================
15
+ # Constants & Theme Config
16
+ # =============================
17
+ HEADER_COLOR = "#4199DA"
18
+ PAPER_COLOR = "#f5f4ef"
 
 
 
 
 
 
 
 
 
19
 
20
+ # =============================
21
+ # Data Loading
22
+ # =============================
23
  data_url = (
24
  "https://raw.githubusercontent.com/Azaya89/holoviz-insights/refs/heads/main/data/"
25
  )
 
47
  }
48
 
49
 
50
+ # =============================
51
+ # Helper Functions
52
+ # =============================
53
+ def format_issue_url(url):
54
+ try:
55
+ return f'<a href="{url}" target="_blank">{url.split("/")[-1]}</a>'
56
+ except Exception:
57
+ return url
58
+
59
+
60
+ # =============================
61
+ # Metric Computation
62
+ # =============================
63
+ def compute_metrics(df):
64
+ metrics = {}
65
+ metrics["first_month"] = df.index[-1].strftime("%B %Y")
66
+ metrics["last_month"] = df.index[0].strftime("%B %Y")
67
+ metrics["total_issues"] = len(df)
68
+ open_issues = df[df["time_to_close"].isna()]
69
+ metrics["still_open"] = len(open_issues)
70
+ metrics["closed"] = len(df) - len(open_issues)
71
+ metrics["avg_close_time"] = int(df["time_to_close"].mean().days)
72
+ metrics["median_close_time"] = int(df["time_to_close"].median().days)
73
+ if "maintainer_responded" in df.columns:
74
+ awaiting = open_issues[~open_issues["maintainer_responded"].fillna(False)]
75
+ metrics["open_awaiting_maintainer"] = len(awaiting)
76
+ else:
77
+ metrics["open_awaiting_maintainer"] = None
78
+ return metrics
79
+
80
+
81
+ # =============================
82
+ # Plot Functions
83
+ # =============================
84
  def create_release_plot(df, repo_name):
85
  from packaging.version import parse
86
 
 
108
  # Set "x1" to now for the last release
109
  if not df.empty:
110
  df.loc[df.index[-1], "x1"] = pd.Timestamp.now(tz=df["published_at"].dt.tz)
111
+ # Add release_span in days
112
+ df["release_span"] = (df["x1"] - df["x0"]).dt.days
113
  df["y0"] = df["y"].cat.codes - 0.4
114
  df["y1"] = df["y"].cat.codes + 0.4
115
  last_release = df.iloc[-1]
 
118
  message = f"🔔 Last release was {days_since} days ago on {last_release['published_at'].date()} ({last_release['tag']})"
119
 
120
  rects = hv.Rectangles(
121
+ df[
122
+ [
123
+ "x0",
124
+ "y0",
125
+ "x1",
126
+ "y1",
127
+ "tag",
128
+ "type",
129
+ "published_at",
130
+ "minor_version",
131
+ "release_span",
132
+ ]
133
+ ],
134
  kdims=["x0", "y0", "x1", "y1"],
135
+ vdims=["tag", "type", "published_at", "minor_version", "release_span"],
136
  )
137
  rects = rects.opts(
138
  color="type",
 
143
  tools=["ycrosshair"],
144
  hover_tooltips=[
145
  ("Release Version", "@tag"),
 
146
  ("Release Type", "@type"),
147
  ("Release Date", "@published_at"),
148
+ ("Release Span (days)", "@release_span"),
149
  ],
150
  xlabel="Date",
151
  ylabel="Minor Version",
 
161
  )
162
 
163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  def create_comparison_plot(df):
165
  monthly_opened = df.resample("ME").size()
166
  monthly_closed = df.dropna(subset=["time_to_close"]).resample("ME").size()
 
174
 
175
 
176
  def create_issues_plot(df):
177
+ # Calculate the number of open issues for each day
178
  df = df.copy()
179
  df["opened_date"] = df.index.normalize()
180
  df["closed_date"] = df["opened_date"] + df["time_to_close"]
 
197
 
198
 
199
  def create_milestone_plot(df):
200
+ # Filter to only include open issues
201
  df = df[df["time_to_close"].isna()]
202
  milestone_counts = df["milestone"].value_counts(dropna=False)
203
+ milestone_counts.name = "Milestone Issues"
204
  return milestone_counts.hvplot.bar(
205
  title="Open Issues by Milestone",
206
  xlabel="Milestone",
 
230
  release_df = release_df.copy()
231
  release_df["year"] = release_df["published_at"].dt.year
232
  releases_per_year = release_df.groupby("year").size()
233
+ releases_per_year.name = "Releases"
234
  return releases_per_year.hvplot.bar(
235
  xlabel="Year",
236
  ylabel="Number of Releases",
237
  title="Releases per Year",
238
+ hover_tooltips=[("Year", "@year"), "@Releases"],
239
  height=300,
240
  width=600,
241
  )
242
 
243
 
244
+ # =============================
245
+ # UI Components (Filters, Selectors, etc.)
246
+ # =============================
247
  styles = {
248
  "box-shadow": "rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px",
249
  "border-radius": "5px",
250
  "padding": "10px",
251
  }
252
 
253
+ maintainer_filter = pmu.RadioButtonGroup(
254
+ label="Maintainer Response",
255
+ options=["All", "Awaiting Maintainer Response", "Maintainer Responded"],
256
+ value="All",
257
+ size="small",
258
+ button_type="primary",
259
+ )
260
+
261
+ status_filter = pmu.RadioButtonGroup(
262
+ label="Issue Status",
263
+ options=["Open Issues", "Closed Issues", "All Issues"],
264
+ value="All Issues",
265
+ size="small",
266
+ button_type="success",
267
+ )
268
+
269
 
270
+ # =============================
271
+ # Views (Indicators, Plots, Table, Header)
272
+ # =============================
273
  @pn.depends(repo_selector)
274
  def indicators_view(repo):
275
  df = repo_dfs[repo]
276
  metrics = compute_metrics(df)
277
+ indicators = [
278
  pn.indicators.Number(
279
  value=metrics["total_issues"],
280
  name="Total Issues Opened",
 
305
  default_color="blue",
306
  styles=styles,
307
  ),
308
+ ]
309
+ if metrics["open_awaiting_maintainer"] is not None:
310
+ indicators.append(
311
+ pn.indicators.Number(
312
+ value=metrics["open_awaiting_maintainer"],
313
+ name="Awaiting Maintainer Response",
314
+ default_color="orange",
315
+ styles=styles,
316
+ )
317
+ )
318
+ return pmu.FlexBox(*indicators)
319
 
320
 
321
  # State variable to store the active tab index
 
346
  return tabs
347
 
348
 
349
+ @pn.depends(repo_selector, status_filter, maintainer_filter)
350
+ def table_view(repo, status, maintainer_resp):
351
  df = repo_dfs[repo].copy()
352
  if status == "Open Issues":
353
  df = df[df["time_to_close"].isna()]
354
  elif status == "Closed Issues":
355
  df = df[df["time_to_close"].notna()]
356
+ # Filter by maintainer response
357
+ if "maintainer_responded" in df.columns and maintainer_resp != "All":
358
+ mask = df["maintainer_responded"].fillna(False)
359
+ if maintainer_resp == "Awaiting Maintainer Response":
360
+ df = df[~mask]
361
+ elif maintainer_resp == "Maintainer Responded":
362
+ df = df[mask]
363
  df["issue_no"] = df["html_url"].apply(format_issue_url)
364
  for col in ["time_to_first_response", "time_to_close"]:
365
  df[f"{col}_str"] = df[col].astype(str)
366
+ # Show maintainer_responded as a column
367
+ if "maintainer_responded" in df.columns:
368
+ df["Maintainer Responded"] = df["maintainer_responded"].map(
369
+ {True: "Yes", False: "No"}
370
+ )
371
+ hidden_cols = [
372
  "html_url",
373
  "time_to_answer",
374
  "time_in_draft",
375
  "time_to_first_response",
376
  "time_to_close",
377
+ "maintainer_responded",
378
+ ]
379
+ else:
380
+ hidden_cols = [
381
+ "html_url",
382
+ "time_to_answer",
383
+ "time_in_draft",
384
+ "time_to_first_response",
385
+ "time_to_close",
386
+ ]
387
+ return pn.widgets.Tabulator(
388
+ df,
389
+ sizing_mode="stretch_width",
390
+ name="Table",
391
+ hidden_columns=hidden_cols,
392
  pagination="remote",
393
  page_size=5,
394
  formatters={"issue_no": "html"},
 
399
  def header_text(repo):
400
  df = repo_dfs[repo]
401
  metrics = compute_metrics(df)
402
+ latest_date = df.index[0].strftime("%B %d, %Y")
403
  text = f"""
404
  ## {repo} Dashboard
405
+ ### Issue Metrics from {metrics["first_month"]} to {latest_date}
406
  """
407
  return text
408
 
409
 
410
+ # =============================
411
+ # Page Layout & App Launch
412
+ # =============================
413
+ note = """The issue metrics shown here are not a full historical record, but represent a snapshot collected automatically at the start of each month.\n Data covers issues from the stated start date up to the stated end date, and is refreshed at the beginning of every new month."""
414
  icon = pn.widgets.TooltipIcon(value=note)
415
  logo = "https://holoviz.org/_static/holoviz-logo.svg"
416
 
417
  logo_pane = pn.pane.Image(logo, width=200, align="center", margin=(10, 0, 10, 0))
418
 
 
 
 
419
  page = pmu.Page(
420
  main=[
421
  header_text,
 
431
  repo_selector,
432
  "## Filter by Issue Status",
433
  status_filter,
434
+ "## Maintainer Response",
435
+ maintainer_filter,
436
  ],
437
  title="HoloViz Issue Metrics Dashboard",
438
  theme_config={