Skip to content

Commit

Permalink
Merge branch 'main' into fix-subnav-css-main
Browse files Browse the repository at this point in the history
  • Loading branch information
Jkrzy authored Aug 31, 2020
2 parents 4bd8427 + 807e9c2 commit fd6d529
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 102 deletions.
38 changes: 19 additions & 19 deletions tock/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from django_webtest import WebTest

from api.views import get_timecards, TimecardList
from api.views import get_timecardobjects, TimecardList
from employees.models import UserData, EmployeeGrade
from hours.factories import (
UserFactory, ReportingPeriodFactory, TimecardFactory, TimecardObjectFactory,
Expand Down Expand Up @@ -83,76 +83,76 @@ def test_timecards_grade_is_populated_when_present(self):
self.assertEqual(res[0]['grade'], 4)

# TODO: test with more diverse data
def test_get_timecards(self):
def test_get_timecardobjects(self):
""" Check that get time cards returns the correct queryset """
# Check with no params
queryset = get_timecards(TimecardList.queryset)
queryset = get_timecardobjects(TimecardList.queryset)
self.assertEqual(len(queryset), 2)
# Check with after param
queryset = get_timecards(TimecardList.queryset,
queryset = get_timecardobjects(TimecardList.queryset,
params={'after': '2020-12-31'})
self.assertEqual(len(queryset), 0)

# Check with date param
queryset = get_timecards(TimecardList.queryset,
queryset = get_timecardobjects(TimecardList.queryset,
params={'date': '2000-01-01'})
self.assertEqual(len(queryset), 0)
queryset = get_timecards(TimecardList.queryset,
queryset = get_timecardobjects(TimecardList.queryset,
params={'date': '2015-06-08'})
self.assertEqual(len(queryset), 1)
# Check with user param
queryset = get_timecards(TimecardList.queryset,
queryset = get_timecardobjects(TimecardList.queryset,
params={'user': '1'})
self.assertEqual(len(queryset), 2)
queryset = get_timecards(TimecardList.queryset,
queryset = get_timecardobjects(TimecardList.queryset,
params={'user': 'aaron.snow'})
self.assertEqual(len(queryset), 2)
queryset = get_timecards(TimecardList.queryset,
queryset = get_timecardobjects(TimecardList.queryset,
params={'user': '22'})
self.assertEqual(len(queryset), 0)
# Check with project param
queryset = get_timecards(TimecardList.queryset,
queryset = get_timecardobjects(TimecardList.queryset,
params={'project': '1'})
self.assertEqual(len(queryset), 2)
queryset = get_timecards(TimecardList.queryset,
queryset = get_timecardobjects(TimecardList.queryset,
params={'project': 'Out Of Office'})
self.assertEqual(len(queryset), 2)
queryset = get_timecards(TimecardList.queryset,
queryset = get_timecardobjects(TimecardList.queryset,
params={'project': '22'})
self.assertEqual(len(queryset), 0)

# Check with before param
queryset = get_timecards(TimecardList.queryset,
queryset = get_timecardobjects(TimecardList.queryset,
params={'before': '2015-06-01'})
self.assertEqual(len(queryset), 1)
queryset = get_timecards(TimecardList.queryset,
queryset = get_timecardobjects(TimecardList.queryset,
params={'before': '2015-05-31'})
self.assertEqual(len(queryset), 0)

# Check with a range using before and after param
queryset = get_timecards(TimecardList.queryset,
queryset = get_timecardobjects(TimecardList.queryset,
params={'after': '2015-06-01', 'before': '2016-05-31'})
self.assertEqual(len(queryset), 1)
queryset = get_timecards(TimecardList.queryset,
queryset = get_timecardobjects(TimecardList.queryset,
params={'after': '2015-06-01', 'before': '2016-06-01'})
self.assertEqual(len(queryset), 2)


def test_get_unsubmitted_timecards(self):
""" Check that get time cards returns the correct queryset """
queryset = get_timecards(
queryset = get_timecardobjects(
TimecardList.queryset,
params={'submitted': 'no'}
)
self.assertEqual(len(queryset), 1)

queryset = get_timecards(
queryset = get_timecardobjects(
TimecardList.queryset,
params={'submitted': 'yes'}
)
self.assertEqual(len(queryset), 2)

queryset = get_timecards(
queryset = get_timecardobjects(
TimecardList.queryset,
params={'submitted': 'foo'}
)
Expand Down
96 changes: 60 additions & 36 deletions tock/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,28 +187,22 @@ class TimecardList(generics.ListAPIView):
serializer_class = TimecardSerializer

def get_queryset(self):
return get_timecards(self.queryset, self.request.query_params)
return get_timecardobjects(self.queryset, self.request.query_params)

def get_timecards(queryset, params=None):
"""
Filter a TimecardObject queryset according to the provided GET
query string parameters:

* if `date` (in YYYY-MM-DD format) is provided, get rows for
which the date falls within their reporting date range.
* if `user` (in either `first.last` or numeric id) is provided,
get rows for that user.
* if `project` is provided as a numeric id or name, get rows for
that project.
def filter_timecards(queryset, params={}):
"""
Filter a queryset of timecards according to the provided query
string parameters.
# include only submitted timecards unless submitted=no in params
submitted = True
if params and 'submitted' in params:
submitted = params['submitted'] != 'no'
queryset = queryset.filter(timecard__submitted=submitted)
* `date`: filter for reporting periods that contain this date
* `user`: either username or numeric id for a user
* `after`: the reporting period ends after the given date
* `before`: the reporting period starts before the given date
"""
submitted_param = params.get("submitted", "yes") # default to only submitted cards
submitted = (submitted_param != "no")
queryset = queryset.filter(submitted=submitted)

if not params:
return queryset
Expand All @@ -217,17 +211,61 @@ def get_timecards(queryset, params=None):
reporting_date = params.get('date')
# TODO: validate YYYY-MM-DD format
queryset = queryset.filter(
timecard__reporting_period__start_date__lte=reporting_date,
timecard__reporting_period__end_date__gte=reporting_date
reporting_period__start_date__lte=reporting_date,
reporting_period__end_date__gte=reporting_date
)

if 'user' in params:
# allow either user name or ID
user = params.get('user')
if user.isnumeric():
queryset = queryset.filter(timecard__user__id=user)
queryset = queryset.filter(user__id=user)
else:
queryset = queryset.filter(timecard__user__username=user)
queryset = queryset.filter(user__username=user)

if 'after' in params:
# get everything after a specified date
after_date = params.get('after')
queryset = queryset.filter(
reporting_period__end_date__gte=after_date
)

if 'before' in params:
# get everything before a specified date
before_date = params.get('before')
queryset = queryset.filter(
reporting_period__start_date__lte=before_date
)

if 'org' in params:
# filter on organization, "0" to include all orgs, "None" for
# "organization IS NULL"
org_id = params.get('org')
if org_id.isnumeric() and org_id != "0": # 0 indicates all organizations, no filtering then
queryset = queryset.filter(user__user_data__organization__id=org_id)
elif org_id.lower() == "none": # the only allowable value that isn't numeric is None
queryset = queryset.filter(user__user_data__organization__isnull=True)

return queryset



def get_timecardobjects(queryset, params={}):
"""
Filter a TimecardObject queryset according to the provided GET
query string parameters:
* `project`: numeric id or name of a project
* `billable`: `True` or `False` to filter for projects that are or aren't billable
"""

# queryset as passed is a queryset of TimecardObjects. Get a queryset of
# the matching Timecards that we can filter...
timecard_queryset = Timecard.objects.filter(timecardobjects__in=queryset)
timecard_queryset = filter_timecards(timecard_queryset, params)
# and now sub-select the matching timecardobjects from our original
# queryset
queryset = queryset.filter(timecard__in=timecard_queryset)

if 'project' in params:
# allow either project name or ID
Expand All @@ -237,27 +275,13 @@ def get_timecards(queryset, params=None):
else:
queryset = queryset.filter(project__name=project)

if 'after' in params:
# get everything after a specified date
after_date = params.get('after')
queryset = queryset.filter(
timecard__reporting_period__end_date__gte=after_date
)

if 'billable' in params:
# only pull records for billable projects
billable = params.get('billable')
queryset = queryset.filter(
project__accounting_code__billable=billable
)

if 'before' in params:
# get everything before a specified date
before_date = params.get('before')
queryset = queryset.filter(
timecard__reporting_period__start_date__lte=before_date
)

return queryset


Expand Down
12 changes: 6 additions & 6 deletions tock/hours/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from api.renderers import stream_csv
from api.views import (ProjectSerializer, TimecardList, UserDataSerializer,
get_timecards)
get_timecardobjects)
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
Expand Down Expand Up @@ -197,7 +197,7 @@ def bulk_timecard_list(request):
"""
Stream all the timecards as CSV.
"""
queryset = get_timecards(TimecardList.queryset, request.GET)
queryset = get_timecardobjects(TimecardList.queryset, request.GET)
serializer = BulkTimecardSerializer()
return stream_csv(queryset, serializer)

Expand All @@ -207,7 +207,7 @@ def slim_bulk_timecard_list(request):
"""
Stream a slimmed down version of all the timecards as CSV.
"""
queryset = get_timecards(TimecardList.queryset, request.GET)
queryset = get_timecardobjects(TimecardList.queryset, request.GET)
serializer = SlimBulkTimecardSerializer()
return stream_csv(queryset, serializer)

Expand All @@ -221,14 +221,14 @@ def general_snippets_only_timecard_list(request):
project__accounting_code__billable=False,
notes__isnull=False
)
queryset = get_timecards(objects, request.GET)
queryset = get_timecardobjects(objects, request.GET)
serializer = GeneralSnippetsTimecardSerializer()
return stream_csv(queryset, serializer)


def timeline_view(request, value_fields=(), **field_alias):
""" CSV endpoint for the project timeline viz. """
queryset = get_timecards(TimecardList.queryset, request.GET)
queryset = get_timecardobjects(TimecardList.queryset, request.GET)

fields = list(value_fields) + [
'timecard__reporting_period__start_date',
Expand Down Expand Up @@ -299,7 +299,7 @@ def admin_bulk_timecard_list(request):
if not request.user.is_superuser:
raise PermissionDenied

queryset = get_timecards(TimecardList.queryset, request.GET)
queryset = get_timecardobjects(TimecardList.queryset, request.GET)
serializer = AdminBulkTimecardSerializer()
return stream_csv(queryset, serializer)

Expand Down
50 changes: 35 additions & 15 deletions tock/tock/templates/utilization/utilization_analytics.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,45 @@
<script src="{% static 'js/plotly-1.54.7.min.js' %}"></script>
<script src="{% static 'js/components/save_csv.js' %}"></script>

<h2>Analytics</h2>

<form class="usa-form">
<div class="usa-form-group">
<label class="usa-label" id="start-date-label" for="start">Start date</label>
<div class="usa-hint" id="start-date-hint">YYYY-MM-DD (after {{ min_date }})</div>
<div class="usa-date-picker">
<input class="usa-input usa-input--inline" id="start" name="start" type="text" aria-describedby="start-date-label start-date-hint" value="{{start_date}}">
</div>
<form class="usa-form maxw-full">

<div class="grid-row grid-gap flex-align-end margin-top-neg-3 ">
<div class="usa-form-group grid-col-2 grid-offset-6">
<label class="usa-label" id="end-date-label" for="org">Organization</label>
<select class="usa-select" name="org" id="org">
{% for id, name in org_choices %}
<option value="{{ id }}"{% if current_org == id %} selected{% endif %}>{{ name }}</option>
{% endfor %}
</select>
</div>

<div class="usa-form-group">
<label class="usa-label" id="end-date-label" for="end">End date</label>
<div class="usa-hint" id="end-date-hint">YYYY-MM-DD</div>
<div class="usa-date-picker">
<input class="usa-input usa-input--inline" id="end" name="end" type="text" aria-describedby="end-date-label end-date-hint" value="{{end_date}}">
<div class="usa-form-group grid-col-2">
<label class="usa-label" id="start-date-label" for="after">Start date</label>
<div class="usa-hint" id="start-date-hint">YYYY-MM-DD (after {{ min_date }})</div>
<div class="usa-date-picker">
<input class="usa-input usa-input--inline" id="after" name="after" type="text" aria-describedby="start-date-label start-date-hint" value="{{start_date}}">
</div>
</div>

<div class="usa-form-group grid-col-2">
<label class="usa-label" id="end-date-label" for="before">End date</label>
<div class="usa-hint" id="end-date-hint">YYYY-MM-DD</div>
<div class="usa-date-picker">
<input class="usa-input usa-input--inline" id="before" name="before" type="text" aria-describedby="end-date-label end-date-hint" value="{{end_date}}">
</div>
</div>
</div>

<input class="usa-button" type="submit" value="Change Dates">
<div class="grid-row">
<div class="grid-col-2 grid-offset-6">
<input class="usa-button" type="submit" value="Select Data">
</div>
</div>
</form>

<div class="grid-row"><div class="grid-col">
<h2>Analytics ({{ current_org_name }})</h2>

<h3>Overall Utilization</h3>
{{utilization_plot|safe}}

Expand All @@ -40,4 +57,7 @@ <h3>Tock Headcount</h3>
{{headcount_plot|safe}}

{% frame_table headcount_data "headcount" as table2 %}{{table2|safe}}

</div></div>

{% endblock %}
Loading

0 comments on commit fd6d529

Please sign in to comment.