Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Implement issue analytics and fixed Label Counts analytics #665

Merged
merged 2 commits into from
Aug 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion labellab-client/src/actions/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {
ANALYTICS_TIME_LABEL_SUCCESS,
ANALYTICS_COUNT_LABEL_FAILURE,
ANALYTICS_COUNT_LABEL_REQUEST,
ANALYTICS_COUNT_LABEL_SUCCESS
ANALYTICS_COUNT_LABEL_SUCCESS,
ISSUE_ANALYTICS_FAILURE,
ISSUE_ANALYTICS_REQUEST,
ISSUE_ANALYTICS_SUCCESS
} from '../constants/index'

import FetchApi from '../utils/FetchAPI'
Expand Down Expand Up @@ -66,3 +69,32 @@ export const getLabelCount = (projectId, callback) => {
return { type: ANALYTICS_COUNT_LABEL_FAILURE, payload: error }
}
}

export const getIssueAnalytics = (projectId, callback) => {
return dispatch => {
dispatch(request())
FetchApi.get(
'/api/v1/issue_analytics/get/' + projectId
)
.then(res => {
dispatch(success(res.data.body))
callback()
})
.catch(err => {
if (err.response) {
err.response.data
? dispatch(failure(err.response.data.msg))
: dispatch(failure(err.response.statusText, null))
}
})
}
function request() {
return { type: ISSUE_ANALYTICS_REQUEST }
}
function success(data) {
return { type: ISSUE_ANALYTICS_SUCCESS, payload: data }
}
function failure(error) {
return { type: ISSUE_ANALYTICS_FAILURE, payload: error }
}
}
50 changes: 46 additions & 4 deletions labellab-client/src/components/project/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { Header, Grid } from 'semantic-ui-react'
import { Bar, Pie } from 'react-chartjs-2'
import { getTimeLabel } from '../../actions/index'
import { Bar, Pie, Doughnut } from 'react-chartjs-2'
import { getTimeLabel, getLabelCount, getIssueAnalytics } from '../../actions/index'
import './css/analytics.css'
class AnalyticsIndex extends Component {
componentDidMount() {
const { match, fetchTimeLabel } = this.props
const { match, fetchTimeLabel, fetchLabelCount, fetchIssueAnalytics } = this.props
fetchTimeLabel(match.params.projectId)
fetchLabelCount(match.params.projectId)
fetchIssueAnalytics(match.params.projectId)
}
render() {
const { timeData, countData, isfetching } = this.props
const { timeData, countData, issueData, isfetching } = this.props
return (
<div className="project-analytics-parent">
<div className="analytics-row">
Expand All @@ -38,6 +40,39 @@ class AnalyticsIndex extends Component {
)}
</div>
</Grid.Column>
<Grid.Column mobile={16} tablet={16} computer={8} className="analytics-column">
<div className="project-analytics-section">
<Header content="Issue Priority Proportions" />
{isfetching ? null : (
<Doughnut
data={issueData['priority']}
options={{ responsive: true, maintainAspectRatio: true }}
/>
)}
</div>
</Grid.Column>
<Grid.Column mobile={16} tablet={16} computer={8} className="analytics-column">
<div className="project-analytics-section">
<Header content="Issue Category Proportions" />
{isfetching ? null : (
<Doughnut
data={issueData['category']}
options={{ responsive: true, maintainAspectRatio: true }}
/>
)}
</div>
</Grid.Column>
<Grid.Column mobile={16} tablet={16} computer={8} className="analytics-column">
<div className="project-analytics-section">
<Header content="Issue Status Proportions" />
{isfetching ? null : (
<Doughnut
data={issueData['status']}
options={{ responsive: true, maintainAspectRatio: true }}
/>
)}
</div>
</Grid.Column>
</Grid>
</div>
</div>
Expand All @@ -56,13 +91,20 @@ const mapStateToProps = state => {
return {
timeData: state.analytics.timeData || {},
countData: state.analytics.countData || {},
issueData: state.analytics.issueData || {},
isfetching: state.analytics.isfetching
}
}
const mapDispatchToProps = dispatch => {
return {
fetchTimeLabel: projectId => {
return dispatch(getTimeLabel(projectId))
},
fetchLabelCount: projectId => {
return dispatch(getLabelCount(projectId))
},
fetchIssueAnalytics: projectId => {
return dispatch(getIssueAnalytics(projectId))
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions labellab-client/src/components/project/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
fetchProject,
getTimeLabel,
getLabelCount,
getIssueAnalytics,
fetchLabels,
fetchCoordinates,
fetchAllTeams,
Expand Down Expand Up @@ -98,6 +99,7 @@ class ProjectIndex extends Component {
fetchTimeLabel,
fetchLabels,
fetchLabelCount,
fetchIssueAnalytics,
fetchCoordinates,
fetchAllTeams,
fetchProjectRoles,
Expand All @@ -109,6 +111,7 @@ class ProjectIndex extends Component {
fetchTimeLabel(match.params.projectId)
fetchLabels(match.params.projectId)
fetchLabelCount(match.params.projectId)
fetchIssueAnalytics(match.params.projectId)
fetchCoordinates(match.params.projectId)
fetchAllTeams(match.params.projectId)
fetchProjectRoles(match.params.projectId)
Expand Down Expand Up @@ -234,6 +237,8 @@ ProjectIndex.propTypes = {
history: PropTypes.object,
fetchProject: PropTypes.func,
fetchTimeLabel: PropTypes.func,
fetchLabelCount: PropTypes.func,
fetchIssueAnalytics: PropTypes.func,
match: PropTypes.object,
actionsLabel: PropTypes.object,
fetchLabels: PropTypes.func,
Expand Down Expand Up @@ -266,6 +271,9 @@ const mapDispatchToProps = dispatch => {
fetchLabelCount: projectId => {
return dispatch(getLabelCount(projectId))
},
fetchIssueAnalytics: projectId => {
return dispatch(getIssueAnalytics(projectId))
},
fetchCoordinates: projectId => {
return dispatch(fetchCoordinates(projectId))
},
Expand Down
6 changes: 5 additions & 1 deletion labellab-client/src/constants/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ export const ANALYTICS_TIME_LABEL_FAILURE = 'ANALYTICS_TIME_LABEL_FAILURE',
ANALYTICS_TIME_LABEL_SUCCESS = 'ANALYTICS_TIME_LABEL_SUCCESS',
ANALYTICS_COUNT_LABEL_FAILURE = 'ANALYTICS_COUNT_LABEL_FAILURE',
ANALYTICS_COUNT_LABEL_REQUEST = 'ANALYTICS_COUNT_LABEL_REQUEST',
ANALYTICS_COUNT_LABEL_SUCCESS = 'ANALYTICS_COUNT_LABEL_SUCCESS'
ANALYTICS_COUNT_LABEL_SUCCESS = 'ANALYTICS_COUNT_LABEL_SUCCESS',
ISSUE_ANALYTICS_FAILURE = 'ISSUE_ANALYTICS_FAILURE',
ISSUE_ANALYTICS_REQUEST = 'ISSUE_ANALYTICS_REQUEST',
ISSUE_ANALYTICS_SUCCESS = 'ISSUE_ANALYTICS_SUCCESS'

24 changes: 22 additions & 2 deletions labellab-client/src/reducers/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import {
ANALYTICS_TIME_LABEL_SUCCESS,
ANALYTICS_COUNT_LABEL_FAILURE,
ANALYTICS_COUNT_LABEL_REQUEST,
ANALYTICS_COUNT_LABEL_SUCCESS
ANALYTICS_COUNT_LABEL_SUCCESS,
ISSUE_ANALYTICS_FAILURE,
ISSUE_ANALYTICS_REQUEST,
ISSUE_ANALYTICS_SUCCESS
} from '../constants/index'
const intialState = {
isfetching: false,
timeData: {},
countData: {}
countData: {},
issueData: {}
}
const analytics = (state = intialState, action) => {
switch (action.type) {
Expand Down Expand Up @@ -45,6 +49,22 @@ const analytics = (state = intialState, action) => {
...state,
isfetching: false
}
case ISSUE_ANALYTICS_REQUEST:
return {
...state,
isfetching: true
}
case ISSUE_ANALYTICS_SUCCESS:
return {
...state,
isfetching: false,
issueData: action.payload
}
case ISSUE_ANALYTICS_FAILURE:
return {
...state,
isfetching: false
}
default:
return state
}
Expand Down
67 changes: 65 additions & 2 deletions labellab-flask/api/controllers/analyticscontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@
from api.helpers.label import (
find_all_by_project_id
)
from api.helpers.issue import (
find_all_issues_by_project_id,
to_json_multiple
)
from api.helpers.analytics import (
issue_labels,
issue_colors,
get_color,
get_months,
get_label_data,
get_label_counts
get_label_counts,
get_issue_data
)
from api.middleware.project_member_access import project_member_only

issue_data_type = ['priority','category','status']

class TimeLabel(MethodView):
"""This class shows the time dependency of a label after being created."""
@jwt_required
Expand Down Expand Up @@ -120,8 +129,62 @@ def get(self, project_id):

return make_response(jsonify(response)), 500

class IssueAnalytics(MethodView):
"""This class shows the count distribution of the issues according to their priority, category and status"""
@jwt_required
@project_member_only
def get(self, project_id):
"""
Handle GET request for this view.
Url --> /api/v1/issue_analytics/get/<int:project_id>
"""
if project_id is None:
response = {
"success": False,
"msg": "Please provide the project id."
}
return make_response(jsonify(response)), 400
try:
issues = find_all_issues_by_project_id(project_id)
issues = to_json_multiple(issues)
if not issues:
response = {
"success": False,
"msg": "No issues present in project."
}
return make_response(jsonify(response)), 200

dataset = get_issue_data(issues)
body = {}
for type in issue_data_type:
body[type] = {
"labels": issue_labels[type],
"datasets": [
{
"label": 'Number of Issues',
"data": dataset[type],
"backgroundColor": issue_colors[type]
}
]
}

response = {
"success": True,
"msg": "IssueData fetched.",
"body": body
}
return make_response(jsonify(response)), 200

except Exception as err:
response = {
"success": False,
"msg": "Something went wrong!"
}
return make_response(jsonify(response)), 500


analyticsController = {
"time_label": TimeLabel.as_view("time_label"),
"label_counts": CountLabel.as_view("label_counts")
"label_counts": CountLabel.as_view("label_counts"),
"issue_analytics": IssueAnalytics.as_view("issue_analytics"),
}
Loading