<jupyter_start><jupyter_text>Triage Issue* This is a helper notebook to go with the triage module* The notebook serves the following purposes  1. It can be used to generate plots showing how well the Kubeflow project is doing triaging issues in a timely fashion  1. It provides snippets of code that can be executed to triage issues  1. It provides snippets of code that can be used to collect some of the information needed by the code* Issues needing Triage are added to the [Needs Triage Project](https://github.com/orgs/kubeflow/projects/26)  * Kubeflow maintainers can look at the Kanban board to identify issues needing triage and triage them  Setup* The cells below import required libraries and do some other housekeeping<jupyter_code>import matplotlib
import importlib
import logging
import sys
import os
import datetime
from dateutil import parser as dateutil_parser
import glob
import json
import numpy as np
import pandas as pd
# A bit of a hack to set the path correctly
sys.path = [os.path.abspath(os.path.join(os.getcwd(), "..", "..", "py"))] + sys.path

logging.basicConfig(level=logging.INFO,
                  format=('%(levelname)s|%(asctime)s'
                        '|%(message)s|%(pathname)s|%(lineno)d|'),
                datefmt='%Y-%m-%dT%H:%M:%S',
                )
logging.getLogger().setLevel(logging.INFO)
%matplotlib inline
import code_intelligence
from code_intelligence import graphql
from issue_triage import triage
importlib.reload(triage)
client = graphql.GraphQLClient()<jupyter_output><empty_output><jupyter_text>Download Issues* The cells below use GitHub's GraphQL API to download all issues in a specified repository that have been updated since `start_time`* The downloaded issues are stored in `.data`; this makes it easy to rerun the processing without needing to redownload the issues<jupyter_code>today = datetime.datetime.now()
today = datetime.datetime(year=today.year, month=today.month, day=today.day)

start_time = today - datetime.timedelta(weeks=24)
issue_filter = {
    "since": start_time.isoformat(),
}
start_time_day =  start_time.strftime("%Y%m%d")
repo = "kubeflow/kubeflow"
issues_dir = os.path.join(os.getcwd(), ".data", "issues", repo, start_time_day)

if os.path.exists(issues_dir):
    logging.info("Issues data already exists; not redownloading")
else:    
    triager = triage.IssueTriage()
    try:        
        triager.download_issues(repo, issues_dir)
    except Exception as download_error:
        raise<jupyter_output>INFO|2019-08-18T19:12:26|kubeflow/kubeflow has a total of 1156 issues|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|379|
INFO|2019-08-18T19:12:26|initializing the shard writer|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|382|
INFO|2019-08-18T19:12:26|Wrote shard 0|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|411|
INFO|2019-08-18T19:12:30|Wrote shard 1|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|411|
INFO|2019-08-18T19:12:38|Wrote shard 2|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|411|
INFO|2019-08-18T19:12:44|Wrote shard 3|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|411|
INFO|2019-08-18T19:12:51|Wrote shard 4|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|411|
INFO|2019-08-18T19:12:59|Wrote shard 5|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|411|
INFO|2019-08-18T19:13:06|Wrote shard 6|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|411|
INFO|2019-08-18T1[...]<jupyter_text>Compute Triage Stats* The cells below compute a time series indicating the number of untriaged issues as a function of time* The graph is used to determine whether the backlog of untriaged issues is increasing or decreasing<jupyter_code>shard_files = glob.glob(os.path.join(issues_dir, "*.json"))

def init_df(offset=0, size=300):
    """Initialize a dataframe of the specified size."""
    return pd.DataFrame({
        "time": [datetime.datetime.now()] * size,
        "delta": np.zeros(size),
    }, index=offset + np.arange(size))


def init_issue_df(offset=0, size=300):
    return pd.DataFrame({
        "created_at": [datetime.datetime(year=2050, month=1, day=1)] * size,
        "triaged_at": [datetime.datetime(year=2050, month=1, day=1)] * size,
        "closed": [bool] * size,
        "url": [""] * size,
        "needs_triage": [bool] * size,
    }, index=offset + np.arange(size))
    
    
def grow_df(df, offset=0, size=300):
    return pd.concat([df, init_df(offset, size)])

num_issues = 0

triage_stats = init_df(size=len(shard_files) * 100 * 2)
issues_df = init_issue_df(size=len(shard_files) * 100)

issues_index = 0

for f in shard_files:
    logging.info("Processing %s", f)
    with open(f) as hf:
        issues = json.load(hf)

    delta = 2 * len(issues)
    if num_issues + delta  > triage_stats.shape[0]:
        # Grow the dataframe
        triage_stats = grow_df(triage_stats, offset=triage_stats.shape[0], size=delta)

    
    if issues_index + len(issues) > issues_df.shape[0]:
        issues_df = grow_df(issues_df, offset= issues_df.shape[0], size=len(issues))    

    for i in issues:        
        info = triage.TriageInfo.from_issue(i)
        
        create_time = dateutil_parser.parse(info.issue["createdAt"])
        
        issues_df["created_at"].at[issues_index] = create_time
        issues_df["url"].at[issues_index] = info.issue["url"]
        issues_df["needs_triage"].at[issues_index] = info.needs_triage
        
        if not info.needs_triage and not info.triaged_at:
            raise ValueError("Issue doesn't need triage but triaged at time not set")
        if info.triaged_at:
            issues_df["triaged_at"].at[issues_index] = info.triaged_at            

        if info.closed_at:
            issues_df["closed"].at[issues_index] = True
        else:
            issues_df["closed"].at[issues_index] = False
        issues_index += 1


issues_df = issues_df[:issues_index]
# Filter issues to issues created after start_time
indexes = issues_df["created_at"] > start_time
issues_df = issues_df.iloc[indexes.values]<jupyter_output><empty_output><jupyter_text>* Compute a series containing the number of untriaged issues as a function of time<jupyter_code>opened = pd.Series([1]*issues_df.shape[0], index=issues_df["created_at"])
triaged_issues = issues_df.iloc[(issues_df["needs_triage"] == False).values]
triaged = pd.Series([-1]*triaged_issues.shape[0], index=triaged_issues["triaged_at"])

deltas = pd.concat([opened, triaged])
deltas = deltas.sort_index()
untriaged = deltas.cumsum()<jupyter_output><empty_output><jupyter_text>Plot Number of Untriaged Issues Over Time* The graph below shows the number of untriaged issues over time* Ideally this graph should be hovering around zero indicating Kubeflow is triaging issues in a timely fashion* If the number of untriaged issues is increasing over time then the Kubeflow project isn't keeping up with incoming issues<jupyter_code>from matplotlib import pylab 
pylab.plot(untriaged.index, untriaged.values, '.-')
pylab.title("Untriaged issues in " + repo)<jupyter_output><empty_output><jupyter_text>Update Needs Triage For Recently Updated Issues* The cells below are used to invoke the code that will update recently updated issues* If an issue needs triage it is added to the [Needs Triage Kanban board](https://github.com/orgs/kubeflow/projects/26)* If an issue in the [Needs Triage Kanban board](https://github.com/orgs/kubeflow/projects/26) has been triaged it is removed from the kanban board<jupyter_code>importlib.reload(triage)
triager=triage.IssueTriage()
repo="kubeflow/kubeflow"

today = datetime.datetime.now()
today = datetime.datetime(year=today.year, month=today.month, day=today.day)

start_time = today - datetime.timedelta(weeks=4)

issue_filter = {
    "since": start_time.isoformat(),
}
triager.triage(repo, issue_filter=issue_filter)<jupyter_output>INFO|2019-09-08T18:28:59|kubeflow/kubeflow has a total of 298 issues|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|379|
INFO|2019-09-08T18:28:59|Processing shard 0|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|545|
INFO|2019-09-08T18:28:59|Issue https://github.com/kubeflow/kubeflow/issues/767:
state:Issue doesn't need attention.
|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|683|
INFO|2019-09-08T18:29:00|Issue https://github.com/kubeflow/kubeflow/issues/1148:
state:Issue doesn't need attention.
|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|683|
INFO|2019-09-08T18:29:00|Issue: https://github.com/kubeflow/kubeflow/issues/1394; fetching all timeline items|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|679|
INFO|2019-09-08T18:29:01|Issue https://github.com/kubeflow/kubeflow/issues/1394:
state:Issue needs triage:
	 Issue needs one of the labels ['improvement/enhancement', 'community/question', 'kind/bug']
|/home/jlewi/git[...]<jupyter_text>Update Needs Triage Kanban Board* The code below processes all issues in the needs triage kanban board and removes issues that have already been triaged<jupyter_code>importlib.reload(triage)
triager = triage.IssueTriage()
triager.update_kanban_board()<jupyter_output>INFO|2019-09-08T18:34:18|Issue https://github.com/kubeflow/kubeflow/issues/4090:
state:Issue needs triage:
	 Issue needs one of the priorities ['priority/p0', 'priority/p1', 'priority/p2', 'priority/p3']
	 Issue needs an area label
|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|682|
INFO|2019-09-08T18:34:18|Issue https://github.com/kubeflow/kubeflow/issues/4090 already in triage project|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|758|
INFO|2019-09-08T18:34:18|Issue https://github.com/kubeflow/kubeflow/issues/4088:
state:Issue needs triage:
	 Issue needs an area label
|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|682|
INFO|2019-09-08T18:34:18|Issue https://github.com/kubeflow/kubeflow/issues/4088 already in triage project|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|758|
INFO|2019-09-08T18:34:18|Issue https://github.com/kubeflow/kubeflow/issues/4087:
state:Issue needs triage:
	 Issue needs an area label
|/home/jlewi/git_code-in[...]<jupyter_text>Triage a Single issue * This cell can be used to triage a single issue* Its useful if you want to find out specific reasons why an issue isn't considered triaged<jupyter_code>importlib.reload(triage)
triager = triage.IssueTriage()
url = "https://github.com/kubeflow/kubeflow/issues/1583"
issue_info = triager.triage_issue(url)<jupyter_output>INFO|2019-09-08T18:33:44|Issue https://github.com/kubeflow/kubeflow/issues/1583:
state:Issue doesn't need attention.
|/home/jlewi/git_code-intelligence/py/issue_triage/triage.py|682|<jupyter_text>Fetch Card Id* This is a snipped which is useful for getting the id of a project card.* We use it to fetch the card id that triage.py should add issues needing triage to.<jupyter_code>project_query="""query projectCards($org: String!, $project: String!) {
  organization(login: $org) {
    projects(last: 1, search: $project) {
      totalCount
      edges {
        node {
          name
          url
          columns(first: 20) {
            totalCount
            pageInfo {
              endCursor
              hasNextPage
            }
            nodes {
              name
              id
            }
          }
        }
      }
    }
  }
}

"""
variables = {
    "org": "kubeflow",
    "project": "Bug Triage",
}
results = client.run_query(project_query, variables)
results<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11723005100717558, 'fraction_numerical': 0.04149736318838074, 'mean_word_length': 4.418266978922716, 'pattern_counts': {'":': 11, '<': 33, '<?xml version=': 0, '>': 36, 'https://': 14, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 7, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '44802', 'license': 'MIT', 'n_tokens_mistral': 4328, 'n_tokens_neox': 3929, 'n_words': 880, 'path': 'Issue_Triage/notebooks/triage.ipynb', 'repo_name': 'zhenghuiwang/code-intelligence', 'size': '11567'}