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

Integration feedbacks changes #13

Draft
wants to merge 51 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d76e783
Add google oauth
thenav56 Aug 3, 2024
a2cca25
Auto-format migrations files as well
thenav56 Aug 5, 2024
895f64c
Add bulk mutations for time tracks
thenav56 Aug 5, 2024
51e85c8
Change TimeTrack -> TimeEntry
thenav56 Aug 5, 2024
c94ab33
Change TimeEntry.TaskType -> TimeEntry.Type
thenav56 Aug 5, 2024
e34fa6c
Remove unused tests.py
thenav56 Aug 5, 2024
86e6b03
Add test for google OAuth
thenav56 Aug 5, 2024
0fea003
Fix session name
thenav56 Aug 6, 2024
962104e
Add Status (TODO|DOING|DONE)
thenav56 Aug 6, 2024
306ee69
User not authenticated in test fix
thenav56 Aug 6, 2024
3663995
Add fake test for showing migrations
thenav56 Aug 6, 2024
eadc7b4
Add TODO move and DOING clone command
thenav56 Aug 6, 2024
061abc9
Integrate codeclimate
thenav56 Aug 7, 2024
9c1cb38
Rename project.client -> project.project_client
thenav56 Aug 7, 2024
8673f30
Preserve TimeEntry client_id data in database
thenav56 Aug 7, 2024
6c40bc0
Add TimeEntry created_at to track TODO tasks
thenav56 Aug 8, 2024
657c5ab
Add /health-check
thenav56 Aug 8, 2024
b65f593
Proper DOING clone
thenav56 Aug 9, 2024
1293497
Fix process_not_done_time_entries timezone issue
thenav56 Aug 13, 2024
17b8e05
Add PROJECT_MANAGEMENT time entry type
thenav56 Aug 13, 2024
ce5c9f6
Store duration as minutes
thenav56 Aug 13, 2024
4d1fdb7
Customize admin panel
thenav56 Aug 14, 2024
254c62a
Change TimeDuration to minutes for both interface
thenav56 Aug 14, 2024
a8ec3a8
Avoid auto string whitespace trimming
thenav56 Aug 14, 2024
64a4650
Add project logo
thenav56 Aug 15, 2024
23efff0
Add EXTERNAL_DISCUSSION as time entry type
thenav56 Aug 20, 2024
2353801
Respond with absolute url for FileField
thenav56 Aug 21, 2024
6c959dc
Add support for duration adjustment for reporting
thenav56 Aug 22, 2024
5ce8f1a
DailyStandUp first draft
thenav56 Aug 22, 2024
3aa1f25
Add events
thenav56 Aug 23, 2024
f8a5e63
Add sentry middleware to track performances
thenav56 Aug 23, 2024
9d405e5
Fix sentry configuration
thenav56 Aug 23, 2024
3d45bdc
Sentry config clean-up
thenav56 Aug 23, 2024
f969ea9
CORS Allow headers for sentry
thenav56 Aug 26, 2024
f643bca
Feedback changes
thenav56 Aug 26, 2024
226ebb3
Add more time entries types
thenav56 Aug 27, 2024
47b537f
Add missing ID on DailyStandUpType->Project
thenav56 Aug 27, 2024
003a736
Track proper user info in sentry
thenav56 Aug 27, 2024
cfec693
Update Status/TimeEntryType labels
thenav56 Aug 27, 2024
ceb5659
Add PROTECT rule
thenav56 Aug 28, 2024
45293a9
Add partial index for active project/contract/tasks
thenav56 Aug 28, 2024
155c077
Return working days count for events
thenav56 Aug 28, 2024
b90cd09
Fix filters
thenav56 Aug 28, 2024
eb36cb4
Use active deadlines only
thenav56 Aug 28, 2024
ff3f35e
Standup (Look for activity to 1 more day)
thenav56 Aug 28, 2024
9b439cd
Add remainingDaysTo Start/End for event
thenav56 Sep 3, 2024
d10bee4
Admin panel changes
thenav56 Sep 3, 2024
f4fffe1
Add SQL view for time entries
thenav56 Sep 6, 2024
c081ef6
Proper handle of Deadline start_date/end_date logic
thenav56 Sep 9, 2024
3ee4731
Make SESSION_COOKIE_AGE configurable
thenav56 Sep 17, 2024
14cbb6f
Add proper date filter in admin panel
thenav56 Sep 21, 2024
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
57 changes: 57 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
version: "2"
checks:
complex-logic:
enabled: true
config:
threshold: 10
file-lines:
enabled: true
config:
threshold: 999
method-complexity:
enabled: true
config:
threshold: 12
method-count:
enabled: true
config:
threshold: 20
method-lines:
enabled: true
config:
threshold: 100
nested-control-flow:
enabled: true
config:
threshold: 4
return-statements:
enabled: true
config:
threshold: 5
argument-count:
enabled: false
similar-code:
enabled: false
identical-code:
enabled: false

plugins:
pep8:
enabled: true
checks:
complexity:
enabled: false
plugins:
fixme:
enabled: true
config:
strings:
- FIXME
- XXX

exclude_patterns:
- "**/migrations/*"
- "**/snapshots/*"
- "**/tests/*"
- "**/wsgi.py"
- "manage.py"
25 changes: 25 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[run]
branch = true
source = .
parallel = true
concurrency = multiprocessing
omit = */migrations/*
*/management/*
*/snaphosts/*
sitecustomize.py
__init.py__
*/wsgi.py
manage.py
.tox/*
**/apps.py
**/urls.py
**/tests.py
**/test_*.py
export/*

[report]
show_missing = true
exclude_lines =
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
extend-ignore = C901, W504
extend-ignore = C901, W504, E701, E203, W503
max-line-length = 125
# NOTE: Update in .pre-commit-config.yaml as well
extend-exclude = .git,__pycache__,old,build,dist,*/migrations/*.py,legacy/,.venv
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
env:
DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }}
run: |
docker-compose -f ./gh-docker-compose.yml run --rm web bash -c 'wait-for-it db:5432 && ./manage.py graphql_schema --out /ci-share/schema-latest.graphql' &&
docker compose -f ./gh-docker-compose.yml run --rm web bash -c 'wait-for-it db:5432 && ./manage.py graphql_schema --out /ci-share/schema-latest.graphql' &&
cmp --silent schema.graphql ./ci-share/schema-latest.graphql || {
echo 'The schema.graphql is not up to date with the latest changes. Please update and push latest';
diff schema.graphql ./ci-share/schema-latest.graphql;
Expand All @@ -62,16 +62,16 @@ jobs:
env:
DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }}
run: |
docker-compose -f ./gh-docker-compose.yml run --rm web bash -c 'wait-for-it db:5432 && ./manage.py makemigrations --check --dry-run' || {
docker compose -f ./gh-docker-compose.yml run --rm web bash -c 'wait-for-it db:5432 && ./manage.py makemigrations --check --dry-run' || {
echo 'There are some changes to be reflected in the migration. Make sure to run makemigrations';
exit 1;
}

# TODO: Run this for CI
# - name: 🤞 Run Test 🧪 & Publish coverage to code climate
# env:
# DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }}
# run: docker-compose -f gh-docker-compose.yml run --rm web /code/scripts/run_tests.sh
- name: 🤞 Run Test 🧪 & Publish coverage to code climate
env:
CC_TEST_REPORTER_ID: ${{ secrets.CODE_CLIMATE_ID }}
DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }}
run: docker compose -f gh-docker-compose.yml run --rm web /code/scripts/run_tests.sh

# Temp fix
# https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md#github-cache
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,5 @@ dmypy.json

# editors
.idea/

assets/
3 changes: 1 addition & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ exclude: |
\.git|
__pycache__|
.*snap_test_.*\.py|
.+\/.+\/migrations\/.*|
\.venv
)

Expand All @@ -30,6 +29,6 @@ repos:
- id: flake8

- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.367
rev: v1.1.378
hooks:
- id: pyright
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
FROM python:3.12-slim-bullseye as base
FROM python:3.12-slim-bullseye

LABEL maintainer="Togglecorp Dev"

ENV PYTHONUNBUFFERED 1
ENV PYTHONUNBUFFERED=1

WORKDIR /code

Expand Down
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- MFA: https://django-mfa.readthedocs.io/en/latest/
38 changes: 36 additions & 2 deletions apps/common/admin.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
import typing

from django.contrib import admin
from django.db import models
from django.http import HttpRequest
from reversion.admin import VersionAdmin as OgVersionAdmin

from .models import UserResource
from .models import Event, UserResource

DjangoModel = typing.TypeVar("DjangoModel", bound=models.Model)


# -- Abstracts
class VersionAdmin(OgVersionAdmin):
history_latest_first = True


class PreventDeleteAdminMixin:
def has_delete_permission(self, request, obj=None):
return False


class UserResourceAdmin(admin.ModelAdmin):
def get_list_display(self, request):
list_display = super().get_list_display(request)
for field in ["created_by", "modified_by"]:
if field not in list_display:
list_display = [
*list_display,
field,
]
return list_display

def get_readonly_fields(self, *args, **kwargs):
readonly_fields = super().get_readonly_fields(*args, **kwargs) # type: ignore[reportAttributeAccessIssue]
return [
Expand All @@ -28,7 +50,7 @@ def save_model(self, request, obj, form, change):
if not change:
obj.created_by = request.user
obj.modified_by = request.user
return super().save_model(request, obj, form, change) # type: ignore[reportAttributeAccessIssue]
super().save_model(request, obj, form, change) # type: ignore[reportAttributeAccessIssue]

def save_formset(self, request, form, formset, change):
if not issubclass(formset.model, UserResource):
Expand All @@ -44,6 +66,9 @@ def save_formset(self, request, form, formset, change):
instance.modified_by = request.user
instance.save()

def get_queryset(self, request: HttpRequest) -> models.QuerySet[DjangoModel]:
return super().get_queryset(request).select_related("created_by", "modified_by")


class UserResourceTabularInline(admin.TabularInline):
def get_readonly_fields(self, *args, **kwargs):
Expand All @@ -60,3 +85,12 @@ def get_readonly_fields(self, *args, **kwargs):
]
)
]


# -- Common Models
@admin.register(Event)
class EventAdmin(VersionAdmin, UserResourceAdmin):
search_fields = ("name",)
list_display = ("name", "type", "start_date", "end_date")
list_filter = ("type",)
ordering = ("start_date",)
56 changes: 56 additions & 0 deletions apps/common/dataloaders.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,70 @@
import typing

# from asgiref.sync import sync_to_async
from django.db import models

# import datetime


# from apps.common.models import Event
# from apps.journal.models import Journal
# from django.utils.functional import cached_property
# from strawberry.dataloader import DataLoader

DjangoModel = typing.TypeVar("DjangoModel", bound=models.Model)


# -- Helper
def load_model_objects(
Model: typing.Type[DjangoModel],
keys: list[int],
) -> list[DjangoModel]:
qs = Model.objects.filter(id__in=keys)
_map = {obj.pk: obj for obj in qs}
return [_map[key] for key in keys]


# -- Common models dataloaders

# def load_user_last_working_date(keys: list[int]) -> list[datetime.date]:
# """
# WITH recursive_days AS (
# SELECT
# DATE_SUB(:input_date, INTERVAL 1 DAY) AS prev_day
# UNION ALL
# SELECT
# DATE_SUB(prev_day, INTERVAL 1 DAY)
# FROM recursive_days
# WHERE
# prev_day NOT IN (
# SELECT
# {Event.date.db_column}
# FROM {Event._meta.db_table}
# WHERE {Event.type.db_column} IN (
# {Event.Type.HOLIDAY.value},
# {Event.Type.RETREAT.value}
# )
# )
# AND DAYOFWEEK(prev_day) NOT IN (1, 7) -- 1 = Sunday, 7 = Saturday
# AND (
# SELECT COUNT(*) FROM recursive_days
# ) < %(NUMBER_OF_WORKING_DAYS_TO_SKIP + 1)s -- Stop after finding N working days
# )
# SELECT MIN(prev_day) AS date_before_n_working_days
# FROM recursive_days;
# """

# recent_user_leave_dates_qs = (
# Journal.as_leave_qs(recent_only=True)
# .filter(user__in=keys)
# .values_list('user', 'date')
# )
# print(recent_user_leave_dates_qs)
# return load_model_objects(User, keys)


class CommonLoader:
# @cached_property
# def load_user_last_working_date(self):
# return DataLoader(load_fn=sync_to_async(load_user_last_working_date))
...
10 changes: 10 additions & 0 deletions apps/common/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import strawberry

from utils.strawberry.enums import get_enum_name_from_django_field

from .models import Event

EventTypeEnum = strawberry.enum(Event.Type, name="EventTypeEnum")


enum_map = {get_enum_name_from_django_field(field): enum for field, enum in ((Event.type, EventTypeEnum),)}
13 changes: 13 additions & 0 deletions apps/common/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import strawberry
import strawberry_django

from .enums import EventTypeEnum
from .models import Event


@strawberry_django.filters.filter(Event, lookups=True)
class EventFilter:
id: strawberry.auto
start_date: strawberry.auto
end_date: strawberry.auto
types: list[EventTypeEnum] # type: ignore[reportInvalidTypeForm]
2 changes: 1 addition & 1 deletion apps/common/management/commands/init_development.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def get_or_create_contractor(self, creator: User, name: str) -> Contractor:
def get_or_create_project(self, creator: User, name: str, client: Client, contractor: Contractor) -> Project:
project, created = Project.objects.get_or_create(
name=name,
client=client,
project_client=client,
contractor=contractor,
defaults=self.get_user_resource_kwargs(creator),
)
Expand Down
49 changes: 49 additions & 0 deletions apps/common/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Generated by Django 4.2.15 on 2024-08-23 04:46

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name="Event",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created_at", models.DateTimeField(auto_now_add=True)),
("modified_at", models.DateTimeField(auto_now=True)),
("name", models.CharField(max_length=225)),
("type", models.PositiveSmallIntegerField(choices=[(1, "Holiday"), (2, "Retreat"), (3, "Misc")], default=1)),
("start_date", models.DateField()),
("end_date", models.DateField()),
(
"created_by",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"ordering": ["-id"],
"abstract": False,
},
),
]
Loading
Loading