Exemplo n.º 1
0
    def get_filters(self):
        data = {}
        params = QueryParameters(self.query_params)
        param_list = json.loads(params.get())

        for param in param_list:
            data[param] = param_list[param]

        logging.info('filters=%s' % (json.dumps(data)))

        return json.dumps(data)
 def generate_query_parameters(self, category="", time=True):
     try:
         query_param = QueryParameters(self.event)
         if time:
             self.param["days"] = query_param.getDays()
             self.param["query_date_until"] = query_param.getDate(
                 'dateUntil')
             self.param["query_date_since"] = query_param.getDate(
                 'dateSince')
         if category == "repo":
             self.param["repo_list"] = query_param.getRepoName().split(
                 ',') if query_param.getRepoName() else []
     except Exception as e:
         payload = {'message': 'Invalid query parameters: {0}'.format(e)}
         return response_formatter(status_code='404', body=payload)
def handler(event, context):
    # Grab the data passed to the lambda function through the browser URL (API Gateway)
    try:
        projectID = (event.get('pathParameters').get('id'))
    except Exception as e:
        print(e)
        payload = {"message": "Id path parameter not given"}
        response = {"statusCode": 400, "body": json.dumps(payload)}
        return response

    redshift = RedshiftConnection()
    cur = redshift.getCursor()

    try:
        redshift.validateProjectID(projectID)
    except Exception:
        redshift.closeConnection()
        payload = {
            "message": "No resource with project ID {} found".format(projectID)
        }
        response = {"statusCode": 404, "body": json.dumps(payload)}
        return response

    try:
        # Grab the query string parameter of offset(days), dateUntil, dateSince, and workTypes
        queryParameters = QueryParameters(event)
        days = queryParameters.getDays()
        dateUntil = queryParameters.getDate('dateUntil')
        dateSince = queryParameters.getDate('dateSince')
        workTypes = queryParameters.getWorktypes()

        workTypeParser = WorkTypeParser(workTypes, projectID)
        workTypeParser.validateWorkTypes(redshift.getCursor(),
                                         redshift.getConn())

        rollingWindowDays = redshift.selectRollingWindow(projectID)
        # Convert rollingWindowDays to rollingWindowWeeks
        rollingWindowWeeks = int(math.floor(rollingWindowDays / 7.0))
        timeInterval = TimeIntervalCalculator(dateUntil, dateSince, days)
        timeInterval.decrementDateSinceWeeks(rollingWindowWeeks)

    except ValueError as err:
        redshift.closeConnection()
        payload = {"message": "{}".format(err)}
        response = {"statusCode": 400, "body": json.dumps(payload)}
        return response

    dateSince = timeInterval.getDateSinceInt()
    dateUntil = timeInterval.getDateUntilInt()

    endDate = dateUntil
    startDate = dateSince

    # Perform rolling average starting from startDate
    rollingWeeks = [startDate]

    secsPerWeek = 604800
    # Insert into rollingWeeks list all the mondays until dateUntil
    while (startDate < endDate):
        startDate += secsPerWeek
        rollingWeeks.append(startDate)

    status_list = get_completion_event_statuses(redshift, projectID)

    issueTypesList = workTypeParser.issueTypesList
    invalidResolutionsList = workTypeParser.invalidResolutionsList

    selectCompletedQuery = """
        SELECT MAX(changed) AS maxdate FROM issue_change 
        WHERE team_project_id = %s
        AND changed < %s
        AND changed >= %s
        AND new_value IN %s
        AND prev_value IN %s
        AND (%s = 0 OR issue_type IN %s)
        AND (%s = 0 OR resolution NOT IN %s)
        GROUP BY issue_key ORDER BY maxdate ASC
        """
    selectCompletedQuery = cur.mogrify(
        selectCompletedQuery,
        (projectID, rollingWeeks[-1], rollingWeeks[0],
         tuple(status_list["completed"]), tuple(
             status_list["working"]), 1 if issueTypesList else 0,
         tuple(issueTypesList) if issueTypesList else
         (None, ), 1 if invalidResolutionsList else 0,
         tuple(invalidResolutionsList) if invalidResolutionsList else
         (None, )))

    try:
        changes = redshift.executeCommitFetchAll(selectCompletedQuery)
    except Exception:
        print("Could not perform query: {}".format(selectCompletedQuery))
        redshift.closeConnection()
        payload = {"message": "Internal error"}
        response = {"statusCode": 500, "body": json.dumps(payload)}
        return response

    changeIndex = 0
    numCompletedIssues = []
    for week in rollingWeeks:
        if week == rollingWeeks[0]:
            continue
        completed = 0
        while (changeIndex < len(changes) and changes[changeIndex][0] < week):
            completed += 1
            changeIndex += 1
        numCompletedIssues.append(completed)

    rollingWeeksUsed = []

    tenthPercentiles = []
    twentiethPercentiles = []
    fiftiethPercentiles = []
    eightiethPercentiles = []
    ninetiethPercentiles = []

    # For all the weeks in rollingWeeks perform the throughput calculations moving the window
    # each time
    for index in range(len(rollingWeeks)):
        if index + rollingWindowWeeks >= len(rollingWeeks):
            # Avoids indexing out of range
            break
        numCompletedIssuesSubset = numCompletedIssues[index:index +
                                                      rollingWindowWeeks]
        sortedWeeks = sorted(numCompletedIssuesSubset)
        # Perform the calculation for each percentile
        # Percentile calculation includes linear interpolation for more accurate values
        # https://commons.apache.org/proper/commons-math/javadocs/api-3.0/org/apache/commons/math3/stat/descriptive/rank/Percentile.html
        ninetiethPercentiles.append(percentile_calculation(0.9, sortedWeeks))
        eightiethPercentiles.append(percentile_calculation(0.8, sortedWeeks))
        fiftiethPercentiles.append(percentile_calculation(0.5, sortedWeeks))
        twentiethPercentiles.append(percentile_calculation(0.2, sortedWeeks))
        tenthPercentiles.append(percentile_calculation(0.1, sortedWeeks))

        week = datetime.datetime.fromtimestamp(
            rollingWeeks[index + rollingWindowWeeks], tz=pytz.utc).isoformat()
        rollingWeeksUsed.append(week)

    # For logging purposes print out the return arrays
    print("Fiftieth Percentiles: {}".format(fiftiethPercentiles))
    print("Eightieth Percentiles: {}".format(eightiethPercentiles))
    print("Ninetieth Percentiles: {}".format(ninetiethPercentiles))

    payload = {
        "fiftieth": zip(rollingWeeksUsed, fiftiethPercentiles),
        "eightieth": zip(rollingWeeksUsed, eightiethPercentiles),
        "ninetieth": zip(rollingWeeksUsed, ninetiethPercentiles),
        "twentieth": zip(rollingWeeksUsed, twentiethPercentiles),
        "tenth": zip(rollingWeeksUsed, tenthPercentiles)
    }

    response = {
        "statusCode": 200,
        "headers": {
            "Access-Control-Allow-Origin":
            "*",  # Required for CORS support to work
            "Access-Control-Allow-Credentials":
            True  # Required for cookies, authorization headers with HTTPS
        },
        "body": json.dumps(payload)
    }

    redshift.closeConnection()
    return response
Exemplo n.º 4
0
def handler(event, context):
    # Grab the data passed to the lambda function through the browser URL (API Gateway)
    try:
        projectID = event.get('pathParameters').get('id')
    except Exception as e:
        payload = {"message": "Id path parameter not given: {]".format(e)}
        return response_formatter(status_code=404, body=payload)

    redshift = RedshiftConnection()

    try:
        redshift.validateProjectID(projectID)
    except Exception as e:
        redshift.closeConnection()
        payload = {"message": "No resource with project ID {} found: {}".format(projectID, e)}
        return response_formatter(status_code=404, body=payload)

    try:
        # Grab the query string parameter of offset(days), dateUntil, dateSince, and workTypes
        queryParameters = QueryParameters(event)
        days = queryParameters.getDays()
        dateUntilParameter = queryParameters.getDate('dateUntil')
        dateSinceParameter = queryParameters.getDate('dateSince')
        workTypes = queryParameters.getWorktypes()

        workTypeParser = WorkTypeParser(workTypes, projectID)
        workTypeParser.validateWorkTypes(redshift.getCursor(), redshift.getConn())

        rollingWindowDays = redshift.selectRollingWindow(projectID)
        # Convert rollingWindowDays to rollingWindowWeeks
        rollingWindowWeeks = int(math.floor(rollingWindowDays / 7.0))
        timeInterval = TimeIntervalCalculator(dateUntilParameter, dateSinceParameter, days)
        timeInterval.decrementDateSinceWeeks(rollingWindowWeeks)

    except ValueError as err:
        redshift.closeConnection()
        payload = {"message": "{}".format(err)}
        return response_formatter(status_code=400, body=payload)

    # Get the actual start and end date after adding rolling weeks in epoch format
    dateSince = timeInterval.getDateSinceInt()
    dateUntil = timeInterval.getDateUntilInt()

    # Generate list of weeks
    endDate = dateUntil
    startDate = dateSince
    rollingWeeks = [startDate]
    secsPerWeek = 604800
    # Insert into weeks all the mondays until dateUntil for label purposes
    while startDate < endDate:
        startDate += secsPerWeek
        rollingWeeks.append(startDate)

    # Init redshift connection
    connection_detail = {
        'dbname': os.environ['DATABASE_NAME'],
        'host': os.environ["CLUSTER_ENDPOINT"],
        'port': os.environ['REDSHIFT_PORT'],
        'user': os.environ['AWS_RS_USER'],
        'password': os.environ['AWS_RS_PASS']
    }

    conn = psycopg2.connect(**connection_detail)

    # Get the sequence for start and end states for current project
    default_state_query = """
    SELECT seq_number
    FROM   team_project, team_work_states
    WHERE  team_project.id = %s
    AND    team_work_states.team_project_id = %s
    AND    (team_work_states.state_name = team_project.default_lead_time_start_state OR 
            team_work_states.state_name = team_project.default_lead_time_end_state)
    ORDER BY seq_number
    """
    with conn:
        with conn.cursor() as cur:
            cur.execute(default_state_query, (projectID, projectID))
            default_state_results = cur.fetchall()
            start_state_seq = default_state_results[0][0]
            end_state_seq = default_state_results[1][0]

    # Get all work states for current project and generate dict for lead time calculation purposes
    work_state_query = """
    SELECT state_name, seq_number
    FROM team_work_states
    WHERE team_project_id = %s
    ORDER BY seq_number
    """
    with conn:
        with conn.cursor() as cur:
            cur.execute(work_state_query, (projectID,))
            work_states_results = cur.fetchall()
            lead_time_states = [work_state for work_state, work_seq in work_states_results if
                                start_state_seq <= work_seq < end_state_seq]
            work_states_dict = {work_seq: work_state for work_state, work_seq in work_states_results}

    # Filter out invalid issue types and resolutions
    issueTypesList = workTypeParser.issueTypesList
    invalidResolutionsList = workTypeParser.invalidResolutionsList

    # Init rolling interval dict for each week for output purposes
    rolling_intervals = {}
    for index in range(len(rollingWeeks)):
        if index + rollingWindowWeeks >= len(rollingWeeks):
            # Avoids indexing out of range
            break
        week = datetime.datetime.fromtimestamp(rollingWeeks[index + rollingWindowWeeks], tz=pytz.utc).isoformat()
        rolling_intervals[week] = {
            "rolling_interval_start": rollingWeeks[index],
            "rolling_interval_end": rollingWeeks[index+rollingWindowWeeks],
            "leadtime": {
                "Overall": []
            }
        }
        for state in lead_time_states:
            rolling_intervals[week]["leadtime"][state] = []

    # Get all issues within time range and their work state changing history
    issue_query = """
    SELECT issue_key,
           listagg(CASE WHEN s1.seq_number IS NULL THEN -1 ELSE s1.seq_number END,',') within group(ORDER BY issue_change.changed) AS prev_number_seq,
           listagg(CASE WHEN s2.seq_number IS NULL THEN -1 ELSE s2.seq_number END,',') within group(ORDER BY issue_change.changed) AS new_number_seq,
           listagg(issue_change.changed,',') within group(ORDER BY issue_change.changed) AS changed_seq
    FROM issue_change
      LEFT JOIN (SELECT team_status_states.team_project_id,
                        team_status_states.status,
                        team_status_states.state_name,
                        team_work_states.seq_number
                 FROM team_status_states
                   LEFT JOIN team_work_states
                          ON team_status_states.team_project_id = team_work_states.team_project_id
                         AND team_status_states.state_name = team_work_states.state_name) s1
             ON s1.team_project_id = issue_change.team_project_id
            AND s1.status = issue_change.prev_value
      LEFT JOIN (SELECT team_status_states.team_project_id,
                        team_status_states.status,
                        team_status_states.state_name,
                        team_work_states.seq_number
                 FROM team_status_states
                   LEFT JOIN team_work_states
                          ON team_status_states.team_project_id = team_work_states.team_project_id
                         AND team_status_states.state_name = team_work_states.state_name) s2
             ON s2.team_project_id = issue_change.team_project_id
            AND s2.status = issue_change.new_value
    WHERE issue_change.team_project_id = %s
    AND   field_name = 'Status'
    AND (%s = 0 OR issue_type IN %s)
    AND (%s = 0 OR resolution NOT IN %s)
    GROUP BY issue_key
    """
    with conn:
        with conn.cursor() as cur:
            cur.execute(issue_query,
                        (projectID,
                         1 if issueTypesList else 0,
                         tuple(issueTypesList) if issueTypesList else (None,),
                         1 if invalidResolutionsList else 0,
                         tuple(invalidResolutionsList) if invalidResolutionsList else (None,)
                         )
                        )
            results = cur.fetchall()

    # Convert results to dict format
    issues = [{"issue_name": result[0],
               "raw_info": zip(result[1].split(","), result[2].split(","), result[3].split(",")),
               "latest_seq": int(result[2].split(",")[-1])
               } for result in results]

    # If latest/current status is not after lead time end state, it means issue is not done and should be filtered out
    # This keeps only finished issues in result set, meaning every issue will now have all worktime states and will be a finished issue
    issues = [issue for issue in issues if issue["latest_seq"] >= end_state_seq]
    # issues = [issue for issue in issues if issue["leadtime"].get("Overall")]

    # still need to filter out issues that were closed before or after dateSince/DataUntil
    counter = 0
    issuesToDelete = []
    #since poping shifts the indices, each time something needs to be poped, must be subtracted by number of pops needing to be done
    numOfPops = 0

    for issue in issues:
        isIssueDeleted = False
        # Init lead time dictionary
        issue["leadtime"] = {el: 0 for el in [item for item in lead_time_states]}
        # Find the first time to get into leadtime state from pre-leadtime state
        for info in issue["raw_info"]:
            prev_seq_number = int(info[0])
            next_seq_number = int(info[1])
            state_transition_time = int(info[2])
            if prev_seq_number < start_state_seq <= next_seq_number < end_state_seq:
                issue["start_state_time"] = state_transition_time
                break
        # Find the last time to get into post-leadtime state from leadtime state
        for info in reversed(issue["raw_info"]):
            prev_seq_number = int(info[0])
            next_seq_number = int(info[1])
            state_transition_time = int(info[2])
            if start_state_seq <= prev_seq_number < end_state_seq <= next_seq_number:
                issue["end_state_time"] = state_transition_time
                break

        #if issue was completed before or after the set amount of time passed into leadtime script, remove it from issues
        if ("end_state_time" in issue) and (issue["end_state_time"] < int(dateSince) or issue["end_state_time"] > int(dateUntil)) and isIssueDeleted == False:
            issuesToDelete.append(counter-numOfPops)
            numOfPops = numOfPops + 1
            isIssueDeleted = True

        # Calculate overall leadtime
        if issue.get("start_state_time") and issue.get("end_state_time"):
            start_time = datetime.datetime.fromtimestamp(issue["start_state_time"])
            end_time = datetime.datetime.fromtimestamp(issue["end_state_time"])
            issue_working_days = TimeIntervalCalculator.workday_diff(start_time, end_time)             
            issue["leadtime"]["Overall"] = float("{0:.2f}".format(issue_working_days))
        # if needed parameters don't exist, remove from loop
        elif isIssueDeleted == False:
            issuesToDelete.append(counter-numOfPops)
            numOfPops = numOfPops + 1
            isIssueDeleted = True            

        #remove issue if it is less than 15 minutes (0.01) to prevent issues from being displayed on chart as 0
        if ("Overall" in issue["leadtime"]) and issue["leadtime"]["Overall"] < 0.01 and isIssueDeleted == False:
            issuesToDelete.append(counter-numOfPops)
            numOfPops = numOfPops + 1
            isIssueDeleted = True

        counter = counter + 1

    #issues = [issue for issue in issues if issue["leadtime"].get("Overall")]

    # Filter out if the issue did not have finish during the time period
    for num in issuesToDelete:
        issues.pop(num)

    for issue in issues:
        # Calculate lead time for each work state
        state_transition_time = -1
        # Loop through the state changing history and add up lead time for all states
        for info in issue["raw_info"]:
            prev_work_state = work_states_dict.get(int(info[0]))
            new_state_transition_time = int(info[2])
            if prev_work_state in lead_time_states and state_transition_time > 0:
                start_time = datetime.datetime.fromtimestamp(state_transition_time)
                end_time = datetime.datetime.fromtimestamp(new_state_transition_time)
                issue_working_days = TimeIntervalCalculator.workday_diff(start_time, end_time)
                issue["leadtime"][prev_work_state] += issue_working_days

            # Update for looping purposes
            state_transition_time = new_state_transition_time

        # Insert issue lead time into all intervals in ascending order for the percentile calculation
        for key, value in rolling_intervals.iteritems():
            if (value["rolling_interval_start"] < issue["start_state_time"] < value["rolling_interval_end"] and
                    value["rolling_interval_start"] < issue["end_state_time"] < value["rolling_interval_end"]):
                for state, leadtime in issue["leadtime"].iteritems():
                    insort(value["leadtime"][state], leadtime)

    # Init Output
    payload = {
        "fiftieth": {},
        "eightieth": {},
        "ninetieth": {}
    }
    for percentile, content in payload.iteritems():
        for state in lead_time_states:
            content[state] = []
        content["Overall"] = []

    # Generate Output
    for key, value in rolling_intervals.iteritems():
        for state, leadtime in value["leadtime"].iteritems():
            payload["fiftieth"][state].append((key, percentile_calculation(0.5, leadtime)))
            payload["eightieth"][state].append((key, percentile_calculation(0.8, leadtime)))
            payload["ninetieth"][state].append((key, percentile_calculation(0.9, leadtime)))

    # Rearrange Output
    for percentile_res, content in payload.iteritems():
        for state, leadtimes in content.iteritems():
            leadtimes.sort(key=itemgetter(0))

    return response_formatter(status_code=200, body=payload)
Exemplo n.º 5
0
def handler(event, context):
    # Grab the data passed to the lambda function through the browser URL (API Gateway)
    try:
        projectID = event.get('pathParameters').get('id')
    except Exception as e:
        payload = {"message": "Id path parameter not given: {]".format(e)}
        return response_formatter(status_code=404, body=payload)

    redshift = RedshiftConnection()

    try:
        redshift.validateProjectID(projectID)
    except Exception as e:
        redshift.closeConnection()
        payload = {
            "message":
            "No resource with project ID {} found: {}".format(projectID, e)
        }
        return response_formatter(status_code=404, body=payload)

    try:
        # Grab the query string parameter of offset(days), dateUntil, dateSince, and workTypes
        queryParameters = QueryParameters(event)
        quarters = queryParameters.getQuarterDates().split(',')
        workTypes = queryParameters.getWorktypes()

        workTypeParser = WorkTypeParser(workTypes, projectID)
        workTypeParser.validateWorkTypes(redshift.getCursor(),
                                         redshift.getConn())

    except ValueError as err:
        redshift.closeConnection()
        payload = {"message": "{}".format(err)}
        return response_formatter(status_code=400, body=payload)

    # Init redshift connection
    connection_detail = {
        'dbname': os.environ['DATABASE_NAME'],
        'host': os.environ["CLUSTER_ENDPOINT"],
        'port': os.environ['REDSHIFT_PORT'],
        'user': os.environ['AWS_RS_USER'],
        'password': os.environ['AWS_RS_PASS']
    }

    conn = psycopg2.connect(**connection_detail)

    # Get the sequence for start and end states for current project
    default_state_query = """
    SELECT seq_number
    FROM   team_project, team_work_states
    WHERE  team_project.id = %s
    AND    team_work_states.team_project_id = %s
    AND    (team_work_states.state_name = team_project.default_lead_time_start_state OR 
            team_work_states.state_name = team_project.default_lead_time_end_state)
    ORDER BY seq_number
    """
    with conn:
        with conn.cursor() as cur:
            cur.execute(default_state_query, (projectID, projectID))
            default_state_results = cur.fetchall()
            start_state_seq = default_state_results[0][0]
            end_state_seq = default_state_results[1][0]

    # Get all work states for current project and generate dict for lead time calculation purposes
    work_state_query = """
    SELECT state_name, seq_number
    FROM team_work_states
    WHERE team_project_id = %s
    ORDER BY seq_number
    """

    with conn:
        with conn.cursor() as cur:
            cur.execute(work_state_query, (projectID, ))
            work_states_results = cur.fetchall()
            lead_time_states = [
                work_state for work_state, work_seq in work_states_results
                if start_state_seq <= work_seq < end_state_seq
            ]
            work_states_dict = {
                work_seq: work_state
                for work_state, work_seq in work_states_results
            }

    # Filter out invalid issue types and resolutions
    issueTypesList = workTypeParser.issueTypesList
    invalidResolutionsList = workTypeParser.invalidResolutionsList

    dateSince = quarters[-1]
    dateUntil = quarters[0]

    # Get all issues within time range and their work state changing history
    issue_query = """
    SELECT issue_key,
           listagg(CASE WHEN s1.seq_number IS NULL THEN -1 ELSE s1.seq_number END,',') within group(ORDER BY issue_change.changed) AS prev_number_seq,
           listagg(CASE WHEN s2.seq_number IS NULL THEN -1 ELSE s2.seq_number END,',') within group(ORDER BY issue_change.changed) AS new_number_seq,
           listagg(issue_change.changed,',') within group(ORDER BY issue_change.changed) AS changed_seq
    FROM issue_change
      LEFT JOIN (SELECT team_status_states.team_project_id,
                        team_status_states.status,
                        team_status_states.state_name,
                        team_work_states.seq_number
                 FROM team_status_states
                   LEFT JOIN team_work_states
                          ON team_status_states.team_project_id = team_work_states.team_project_id
                         AND team_status_states.state_name = team_work_states.state_name) s1
             ON s1.team_project_id = issue_change.team_project_id
            AND s1.status = issue_change.prev_value
      LEFT JOIN (SELECT team_status_states.team_project_id,
                        team_status_states.status,
                        team_status_states.state_name,
                        team_work_states.seq_number
                 FROM team_status_states
                   LEFT JOIN team_work_states
                          ON team_status_states.team_project_id = team_work_states.team_project_id
                         AND team_status_states.state_name = team_work_states.state_name) s2
             ON s2.team_project_id = issue_change.team_project_id
            AND s2.status = issue_change.new_value
    WHERE issue_change.team_project_id = %s
    AND   field_name = 'Status'
    AND (%s = 0 OR issue_type IN %s)
    AND (%s = 0 OR resolution NOT IN %s)
    GROUP BY issue_key
    """
    with conn:
        with conn.cursor() as cur:
            cur.execute(
                issue_query,
                (projectID, 1 if issueTypesList else 0,
                 tuple(issueTypesList) if issueTypesList else
                 (None, ), 1 if invalidResolutionsList else 0,
                 tuple(invalidResolutionsList) if invalidResolutionsList else
                 (None, )))
            results = cur.fetchall()

    # Convert results to dict format
    issues = [{
        "issue_name":
        result[0],
        "raw_info":
        zip(result[1].split(","), result[2].split(","), result[3].split(",")),
        "latest_seq":
        int(result[2].split(",")[-1])
    } for result in results]

    # If latest/current status is not after lead time end state, it means issue is not done and should be filtered out
    # This keeps only finished issues in result set, meaning every issue will now have all worktime states and will be a finished issue
    issues = [
        issue for issue in issues if issue["latest_seq"] >= end_state_seq
    ]

    # still need to filter out issues that were closed before or after dateSince/DataUntil
    counter = 0
    issuesToDelete = []
    #since poping shifts the indices, each time something needs to be poped, must be subtracted by number of pops needing to be done
    numOfPops = 0

    for issue in issues:
        isIssueDeleted = False
        # Init lead time dictionary
        issue["leadtime"] = {
            el: 0
            for el in [item for item in lead_time_states]
        }
        # Find the first time to get into leadtime state from pre-leadtime state
        for info in issue["raw_info"]:
            prev_seq_number = int(info[0])
            next_seq_number = int(info[1])
            state_transition_time = int(info[2])
            if prev_seq_number < start_state_seq <= next_seq_number < end_state_seq:
                issue["start_state_time"] = state_transition_time
                break
        # Find the last time to get into post-leadtime state from leadtime state
        for info in reversed(issue["raw_info"]):
            prev_seq_number = int(info[0])
            next_seq_number = int(info[1])
            state_transition_time = int(info[2])
            if start_state_seq <= prev_seq_number < end_state_seq <= next_seq_number:
                issue["end_state_time"] = state_transition_time
                break

        #if issue was completed before or after the set amount of time passed into leadtime script, remove it from issues
        if ("end_state_time"
                in issue) and (issue["end_state_time"] < int(dateSince)
                               or issue["end_state_time"] > int(dateUntil)
                               ) and isIssueDeleted == False:
            issuesToDelete.append(counter - numOfPops)
            numOfPops = numOfPops + 1
            isIssueDeleted = True

        # Calculate overall leadtime
        if issue.get("start_state_time") and issue.get("end_state_time"):
            start_time = datetime.datetime.fromtimestamp(
                issue["start_state_time"])
            end_time = datetime.datetime.fromtimestamp(issue["end_state_time"])
            issue_working_days = TimeIntervalCalculator.workday_diff(
                start_time, end_time)
            issue["leadtime"]["Overall"] = float(
                "{0:.2f}".format(issue_working_days))
        # if needed parameters don't exist, remove from loop
        elif isIssueDeleted == False:
            issuesToDelete.append(counter - numOfPops)
            numOfPops = numOfPops + 1
            isIssueDeleted = True

        #remove issue if it is less than 15 minutes (0.01) to prevent issues from being displayed on chart as 0
        if (
                "Overall" in issue["leadtime"]
        ) and issue["leadtime"]["Overall"] < 0.01 and isIssueDeleted == False:
            issuesToDelete.append(counter - numOfPops)
            numOfPops = numOfPops + 1
            isIssueDeleted = True

        counter = counter + 1

    # Filter out if the issue did not have finish during the time period
    for num in issuesToDelete:
        issues.pop(num)

    for issue in issues:
        # Calculate lead time for each work state
        state_transition_time = -1
        # Loop through the state changing history and add up lead time for all states
        for info in issue["raw_info"]:
            prev_work_state = work_states_dict.get(int(info[0]))
            new_state_transition_time = int(info[2])
            if prev_work_state in lead_time_states and state_transition_time > 0:
                start_time = datetime.datetime.fromtimestamp(
                    state_transition_time)
                end_time = datetime.datetime.fromtimestamp(
                    new_state_transition_time)
                issue_working_days = TimeIntervalCalculator.workday_diff(
                    start_time, end_time)
                issue["leadtime"][prev_work_state] += issue_working_days

            # Update for looping purposes
            state_transition_time = new_state_transition_time

    payload = []
    #create graph data set from data
    for issue in issues:
        obj = {
            'name': issue['issue_name'],
            'workingDays': issue['leadtime']['Overall'],
            'endTime': issue['end_state_time']
        }
        payload.append(obj)

    return response_formatter(status_code=200, body=payload)
Exemplo n.º 6
0
def handler(event, context):
    # Grab the data passed to the lambda function through the browser URL (API Gateway)
    try:
        projectID = (event.get('pathParameters').get('id'))
    except Exception as e:
        print(e)
        payload = {"message": "Id path parameter not given"}
        response = {"statusCode": 400, "body": json.dumps(payload)}
        return response

    redshift = RedshiftConnection()
    cur = redshift.getCursor()

    try:
        redshift.validateProjectID(projectID)
    except Exception:
        redshift.closeConnection()
        payload = {
            "message": "No resource with project ID {} found".format(projectID)
        }
        response = {"statusCode": 404, "body": json.dumps(payload)}
        return response

    try:
        # Grab the query string parameter of offset(days), dateUntil, dateSince, and workTypes
        queryParameters = QueryParameters(event)
        days = queryParameters.getDays()
        dateUntil = queryParameters.getDate('dateUntil')
        dateSince = queryParameters.getDate('dateSince')
        workTypes = queryParameters.getWorktypes()

        workTypeParser = WorkTypeParser(workTypes, projectID)
        workTypeParser.validateWorkTypes(redshift.getCursor(),
                                         redshift.getConn())

        timeInterval = TimeIntervalCalculator(dateUntil, dateSince, days)
        timeInterval.decrementDateSinceWeeks(1)

    except ValueError as err:
        redshift.closeConnection()
        payload = {"message": "{}".format(err)}
        response = {"statusCode": 400, "body": json.dumps(payload)}
        return response

    dateSince = timeInterval.getDateSinceInt()
    dateUntil = timeInterval.getDateUntilInt()

    endDate = dateUntil
    startDate = dateSince
    weeks = [startDate]

    secsPerWeek = 604800
    # Insert into weeks all the mondays until dateUntil
    while (startDate < endDate):
        startDate += secsPerWeek
        weeks.append(startDate)

    status_list = get_completion_event_statuses(redshift, projectID)

    issueTypesList = workTypeParser.issueTypesList
    invalidResolutionsList = workTypeParser.invalidResolutionsList

    selectCompletedQuery = """
    SELECT MAX(changed) AS maxdate, issue_key FROM issue_change 
    WHERE team_project_id = %s
    AND changed < %s
    AND changed >= %s
    AND new_value IN %s
    AND prev_value IN %s
    AND (%s = 0 OR issue_type IN %s)
    AND (%s = 0 OR resolution NOT IN %s)
    GROUP BY issue_key ORDER BY maxdate ASC
    """
    selectCompletedQuery = cur.mogrify(
        selectCompletedQuery,
        (projectID, weeks[-1], weeks[0], tuple(status_list["completed"]),
         tuple(status_list["working"]), 1 if issueTypesList else 0,
         tuple(issueTypesList) if issueTypesList else
         (None, ), 1 if invalidResolutionsList else 0,
         tuple(invalidResolutionsList) if invalidResolutionsList else
         (None, )))
    try:
        changes = redshift.executeCommitFetchAll(selectCompletedQuery)
    except Exception:
        print("Could not perform query: {}".format(selectCompletedQuery))
        redshift.closeConnection()
        payload = {"message": "Internal server error"}
        response = {"statusCode": 500, "body": json.dumps(payload)}
        return response

    ticketsByWeekPayload = []
    ticketsByWeekPayload = jira_throughput_tickets.buildTicketPayload(
        changes, weeks)

    changeIndex = 0
    payload = []
    organizedTotals = []

    for week in weeks:
        if week == weeks[0]:
            continue
        completed = 0
        while (changeIndex < len(changes) and changes[changeIndex][0] < week):
            completed += 1
            changeIndex += 1
        # isoformat implicitly assumes utc time without appending trailing 'Z'
        weekStr = datetime.datetime.fromtimestamp(
            week, tz=pytz.utc).isoformat() + "Z"
        payload.append([weekStr, completed])
        organizedTotals.append(completed)

    organizedTotals = sorted(organizedTotals)
    lengthOfDataSet = len(organizedTotals)

    # Calculate striaght percentile values using the R7 statistical method
    # https://en.wikipedia.org/wiki/Quantile (find: R-7)
    ninetiethPercentilesStraightPoint = R7PercentileCalculator(
        90.0, organizedTotals, lengthOfDataSet)
    eightiethPercentilesStraightPoint = R7PercentileCalculator(
        80.0, organizedTotals, lengthOfDataSet)
    fiftiethPercentilesStraightPoint = R7PercentileCalculator(
        50.0, organizedTotals, lengthOfDataSet)
    twentiethPercentilesStraightPoint = R7PercentileCalculator(
        20.0, organizedTotals, lengthOfDataSet)
    tenthPercentilesStraightPoint = R7PercentileCalculator(
        10.0, organizedTotals, lengthOfDataSet)

    #make each "straight percentile" an array of values of equal length to
    ninetiethPercentilesStraight = [ninetiethPercentilesStraightPoint
                                    ] * lengthOfDataSet
    eightiethPercentilesStraight = [eightiethPercentilesStraightPoint
                                    ] * lengthOfDataSet
    fiftiethPercentilesStraight = [fiftiethPercentilesStraightPoint
                                   ] * lengthOfDataSet
    twentiethPercentilesStraight = [twentiethPercentilesStraightPoint
                                    ] * lengthOfDataSet
    tenthPercentilesStraight = [tenthPercentilesStraightPoint
                                ] * lengthOfDataSet

    payload.append(["fiftiethStraight", fiftiethPercentilesStraight])
    payload.append(["eightiethStraight", eightiethPercentilesStraight])
    payload.append(["ninetiethStraight", ninetiethPercentilesStraight])
    payload.append(["twentiethStraight", twentiethPercentilesStraight])
    payload.append(["tenthStraight", tenthPercentilesStraight])

    #since 2 outputs needed to be encoded into body of response, separate by string to parse and break into
    #2 outputs on front-end
    newPayload = payload + ["tickets"] + ticketsByWeekPayload

    response = {
        "statusCode": 200,
        "headers": {
            "Access-Control-Allow-Origin":
            "*",  # Required for CORS support to work
            "Access-Control-Allow-Credentials":
            True  # Required for cookies, authorization headers with HTTPS
        },
        "body": json.dumps(newPayload),
    }

    redshift.closeConnection()
    return response
Exemplo n.º 7
0
def handler(event, context):
    # Grab the data passed to the lambda function through the browser URL (API Gateway)
    try:
        projectID = (event.get('pathParameters').get('id'))
    except Exception as e:
        print (e)
        payload = {"message": "Id path parameter not given"}
        response={
            "statusCode": 400,
            "body": json.dumps(payload)
        }
        return response

    redshift = RedshiftConnection()
    cur = redshift.getCursor()

    selectIDQuery = cur.mogrify("SELECT name, id FROM team_project WHERE id = %s", (projectID,))

    try:
        IDResults = redshift.executeCommitFetchAll(selectIDQuery)
    except Exception:
        redshift.closeConnection()
        payload = {"message": "Internal Error"}
        response={
            "statusCode": 500,
            "body": json.dumps(payload)
        }
        return response

    if not IDResults:
        redshift.closeConnection()
        payload = {"message": "No resource with project ID {} found".format(projectID)}
        response={
            "statusCode": 404,
            "body": json.dumps(payload)
        }
        return response

    # Grab the query string parameter of dateUntil, dateSince, and offset
    # If omitted dateUntil will be set to current date and time
    # If omitted dateSince will be 90 days before dateUntil
    # These repetitive try/catch blocks are necessary as Lambda throw an exception if the specified
    # parameter is not given.
    try:
        days = int(event.get('queryStringParameters').get('days'))
    except Exception as e:
        # If days not given, set it to default of 90
        days = 90
    try:
        dateUntil = event.get('queryStringParameters').get('dateUntil')
    except Exception as e:
        dateUntil = None
    try:
        dateSince = event.get('queryStringParameters').get('dateSince')
    except Exception as e:
        dateSince = None
    try:
        workTypes = event.get('queryStringParameters').get('workTypes')
    except Exception as e:
        # If workTypes not given, all issue types will be returned
        workTypes = None

    try:
        # Try to decode the given date parameters, if undecodable throw exception
        query_param = QueryParameters()
        dateUntil = query_param.decodeDateParam(dateUntil)
        dateSince = query_param.decodeDateParam(dateSince)
    except ValueError as err:
        redshift.closeConnection()
        payload = {"message": "{}".format(err)}
        response={
            "statusCode": 400,
            "body": json.dumps(payload)
        }
        return response


    workTypeParser = WorkTypeParser(workTypes,projectID)
    try:
        workTypeParser.validateWorkTypes(redshift.getCursor(), redshift.getConn())
    except ValueError as err:
        redshift.closeConnection()
        payload = {"message": "{}".format(err)}
        response={
            "statusCode": 400,
            "body": json.dumps(payload)
        }
        return response

    try:
        timeInterval = TimeIntervalCalculator(dateUntil, dateSince, days)
        timeInterval.decrementDateSinceWeeks(1)
    except ValueError as err:
        payload = {"message": "{}".format(err)}
        response={
            "statusCode": 400,
            "body": json.dumps(payload)
        }
        return response

    dateSince = timeInterval.getDateSinceInt()
    dateUntil = timeInterval.getDateUntilInt()

    endDate = dateUntil
    startDate = dateSince
    weeks = [startDate]

    secsPerWeek = 604800
    # Insert into weeks all the mondays until dateUntil
    while (startDate < endDate):
        startDate += secsPerWeek
        weeks.append(startDate)

    issueTypesList = workTypeParser.issueTypesList

    status_list = get_completion_event_statuses(redshift, projectID)

    try:
        selectOpenClosedQuery = """
        SELECT changed, new_value, issue_key
        FROM  issue_change 
        WHERE team_project_id = %(project_id)s
        AND   prev_value = ''
        AND   changed < %(date_until)s
        AND   field_name = 'Status'
        AND (%(issue_type_flag)s = 0 OR issue_type IN %(issue_type_list)s)
        UNION
        SELECT completed.changed,
               'Completed' AS new_value,
               completed.issue_key
        FROM (SELECT MAX(changed) AS changed,
                     'Completed' AS new_value,
                     issue_key
              FROM  issue_change
              WHERE team_project_id = %(project_id)s
              AND   new_value IN %(completed_status)s
              AND   changed < %(date_until)s
              AND   field_name = 'Status'
              AND (%(issue_type_flag)s = 0 OR issue_type IN %(issue_type_list)s)
              GROUP BY issue_key) completed
          LEFT JOIN issue_change uncompleted
                 ON uncompleted.issue_key = completed.issue_key
                AND uncompleted.changed > completed.changed
                AND uncompleted.new_value NOT IN %(completed_status)s
        WHERE uncompleted.changed IS NULL
        ORDER BY changed
        """
        selectOpenClosedQuery = cur.mogrify(selectOpenClosedQuery, {
            "project_id": projectID,
            "date_until": dateUntil,
            "completed_status": tuple(status_list["completed"]),
            "issue_type_list": tuple(issueTypesList) if issueTypesList else (None,),
            "issue_type_flag": 1 if issueTypesList else 0
        })
        cur.execute(selectOpenClosedQuery)
        changes = cur.fetchall()
    except Exception as e:
        print ("ERROR: {}".format(e))
        redshift.closeConnection()
        payload = {"message": "Internal server error"}
        response={
            "statusCode": 500,
            "body": json.dumps(payload)
        }
        return response

    changeIndex = 0

    payload = {"Created": [],
               "Completed": []}
    completed = 0
    created = 0
    for week in weeks:
        while changeIndex < len(changes) and changes[changeIndex][0] < week:
            newValue = changes[changeIndex][1]
            if newValue == 'Completed':
                completed += 1
            elif newValue == 'Open':
                created += 1
            changeIndex += 1

        # isoformat implicitly assumes utc time without appending trailing 'Z'
        weekStr = datetime.datetime.fromtimestamp(week, tz=pytz.utc).isoformat()
        payload["Created"].append([weekStr, created])
        payload["Completed"].append([weekStr, completed])

    response={
        "statusCode": 200,
        "headers": {
            "Access-Control-Allow-Origin" : "*", # Required for CORS support to work
            "Access-Control-Allow-Credentials" : True # Required for cookies, authorization headers with HTTPS
        },
        "body": json.dumps(payload)
    }

    redshift.closeConnection()
    return response