From d8db57f1320763b36576bc0c01044e8048f769be Mon Sep 17 00:00:00 2001 From: nearbeach Date: Mon, 13 May 2024 21:18:34 +1000 Subject: [PATCH 1/7] Improvements to Unit Tests for the backend --- NearBeach/fixtures/NearBeach_basic_setup.json | 172 +++++++++++++++++- .../tests_user_permissions/test_admin_user.py | 8 + ...test_redirection_of_non_logged_in_users.py | 6 + .../test_team_leader.py | 8 + NearBeach/views/user_job_views.py | 21 +++ 5 files changed, 211 insertions(+), 4 deletions(-) diff --git a/NearBeach/fixtures/NearBeach_basic_setup.json b/NearBeach/fixtures/NearBeach_basic_setup.json index df91c717e..a75b99935 100644 --- a/NearBeach/fixtures/NearBeach_basic_setup.json +++ b/NearBeach/fixtures/NearBeach_basic_setup.json @@ -1986,6 +1986,114 @@ "is_deleted": false } }, +{ + "model": "NearBeach.objectassignment", + "pk": 35, + "fields": { + "assigned_user": 1, + "group_id": null, + "requirement": null, + "requirement_item": null, + "project": 1, + "task": null, + "kanban_board": null, + "kanban_card": null, + "request_for_change": null, + "customer": null, + "organisation": null, + "change_task": null, + "meta_object": null, + "meta_object_title": "", + "meta_object_status": "", + "link_relationship": "", + "parent_link": "", + "date_created": "2024-05-13T10:29:17.681Z", + "date_modified": "2024-05-13T10:29:17.681Z", + "change_user": 1, + "is_deleted": false + } +}, +{ + "model": "NearBeach.objectassignment", + "pk": 36, + "fields": { + "assigned_user": 5, + "group_id": null, + "requirement": null, + "requirement_item": null, + "project": null, + "task": 2, + "kanban_board": null, + "kanban_card": null, + "request_for_change": null, + "customer": null, + "organisation": null, + "change_task": null, + "meta_object": null, + "meta_object_title": "", + "meta_object_status": "", + "link_relationship": "", + "parent_link": "", + "date_created": "2024-05-13T10:30:40.078Z", + "date_modified": "2024-05-13T10:30:40.078Z", + "change_user": 1, + "is_deleted": false + } +}, +{ + "model": "NearBeach.objectassignment", + "pk": 37, + "fields": { + "assigned_user": 3, + "group_id": null, + "requirement": null, + "requirement_item": null, + "project": null, + "task": 2, + "kanban_board": null, + "kanban_card": null, + "request_for_change": null, + "customer": null, + "organisation": null, + "change_task": null, + "meta_object": null, + "meta_object_title": "", + "meta_object_status": "", + "link_relationship": "", + "parent_link": "", + "date_created": "2024-05-13T10:41:58.777Z", + "date_modified": "2024-05-13T10:41:58.777Z", + "change_user": 2, + "is_deleted": false + } +}, +{ + "model": "NearBeach.objectassignment", + "pk": 38, + "fields": { + "assigned_user": 2, + "group_id": null, + "requirement": null, + "requirement_item": null, + "project": null, + "task": 2, + "kanban_board": null, + "kanban_card": null, + "request_for_change": null, + "customer": null, + "organisation": null, + "change_task": null, + "meta_object": null, + "meta_object_title": "", + "meta_object_status": "", + "link_relationship": "", + "parent_link": "", + "date_created": "2024-05-13T10:42:14.120Z", + "date_modified": "2024-05-13T10:42:14.120Z", + "change_user": 2, + "is_deleted": false + } +}, { "model": "NearBeach.objectnote", "pk": 1, @@ -2962,6 +3070,54 @@ "is_deleted": false } }, +{ + "model": "NearBeach.userjob", + "pk": 1, + "fields": { + "username": 1, + "job_date": "2024-05-14", + "job_sort_number": 0, + "kanban_card": null, + "project": 1, + "task": null, + "date_created": "2024-05-13T10:29:27.540Z", + "date_modified": "2024-05-13T10:32:17.413Z", + "change_user": 1, + "is_deleted": false + } +}, +{ + "model": "NearBeach.userjob", + "pk": 2, + "fields": { + "username": 5, + "job_date": "2024-05-13", + "job_sort_number": 0, + "kanban_card": null, + "project": null, + "task": 2, + "date_created": "2024-05-13T10:30:51.400Z", + "date_modified": "2024-05-13T10:30:51.400Z", + "change_user": 5, + "is_deleted": false + } +}, +{ + "model": "NearBeach.userjob", + "pk": 3, + "fields": { + "username": 2, + "job_date": "2024-05-14", + "job_sort_number": 0, + "kanban_card": null, + "project": null, + "task": 2, + "date_created": "2024-05-13T10:42:21.906Z", + "date_modified": "2024-05-13T10:42:30.668Z", + "change_user": 2, + "is_deleted": false + } +}, { "model": "NearBeach.usersetting", "pk": 1, @@ -4862,8 +5018,8 @@ "model": "auth.user", "pk": 5, "fields": { - "password": "pbkdf2_sha256$390000$wSOIKxIzbfTVG7Z6TRKQ0n$QrhNVyNQ5C3RW8rSVkxleNmyCqECJX48uqenvvBTIdQ=", - "last_login": null, + "password": "pbkdf2_sha256$720000$lme60tWetgq1EdCCjo4iPZ$tGR4stCLa56Rt/LjtHYKCff2RDtJ0RJ4pFr4fC24TXo=", + "last_login": "2024-05-13T10:30:22.990Z", "is_superuser": false, "username": "read_only", "first_name": "Read", @@ -5312,6 +5468,14 @@ "model": "session" } }, +{ + "model": "sessions.session", + "pk": "1dxxe5glzsbvq94spcivlz28kvq8vazx", + "fields": { + "session_data": ".eJxVjEEOgyAQRe_CuiEoI4wuu-8ZyABjtTXagKya3r2SuGi3_73_3sJR2SdXMic3RzGITlx-N0_hyWsF8UHrfZNhW_c0e1kVedIsb1vk5Xq6f4GJ8nS8jUdq0IIhrbANYDmy5l5FGAMCtR1Y4xtkYgjYKd1Ab9AAe2DSBPqIztnl8uJUs2IYacn8-QK2uz9a:1s6Swp:5KDFXsDZsmVAprYtI7TP_dX-ddlKrYDYujwVQTCZbSU", + "expire_date": "2024-05-27T10:30:23.003Z" + } +}, { "model": "sessions.session", "pk": "4ihdjbrhcx7hokg3uisrpinxhca0e34d", @@ -5340,8 +5504,8 @@ "model": "sessions.session", "pk": "922i2920gbdv4xctpizrk8pypiaysael", "fields": { - "session_data": ".eJxVjEEOwiAQRe_C2hAYoNQu3XsGwgyDrZrWQFkZ726bdKHb_95_bxFiW8fQKpcwJTEILU6_G0Z68LyDdI_zbZG0zGuZUO6KPGiV1yXx83K4f4Ex1nF7O0Ai68FFdqlPkdH1nBUmC-SzNqAUeaMMGtuBBsAz6q7LtrfaGqdoi0411PbismfFsJbGny-LGT7k:1s6R9f:Bnve0zi8T7R8BLUIPjW7DFbBUFWSyOuRHKDjTJhqJKw", - "expire_date": "2024-05-27T08:35:31.102Z" + "session_data": ".eJxVjEEOwiAQRe_C2hAYoNQu3XsGwgyDrZrWQFkZ726bdKHb_95_bxFiW8fQKpcwJTEILU6_G0Z68LyDdI_zbZG0zGuZUO6KPGiV1yXx83K4f4Ex1nF7O0Ai68FFdqlPkdH1nBUmC-SzNqAUeaMMGtuBBsAz6q7LtrfaGqdoi0411PbismfFsJbGny-LGT7k:1s6Sva:uc1eEmvvEv9_L46aDGeLvGYfSLgwPVARk6sdNAHWLGk", + "expire_date": "2024-05-27T10:29:06.063Z" } }, { diff --git a/NearBeach/tests/tests_user_permissions/test_admin_user.py b/NearBeach/tests/tests_user_permissions/test_admin_user.py index 86e8cec0b..e9f2e87e8 100644 --- a/NearBeach/tests/tests_user_permissions/test_admin_user.py +++ b/NearBeach/tests/tests_user_permissions/test_admin_user.py @@ -106,6 +106,14 @@ def test_basic_permissions_as_admin(self): URLTest("profile_update_data", [], {"username": 1, "first_name": "Admin", "last_name": "Admin", "theme": "dark"}, 200, "POST"), URLTest("diagnostic_information_email_test", [], {}, 200, "POST"), URLTest("diagnostic_information", [], {}, 200, "GET"), + URLTest("my_planner", [], {}, 200, "GET"), + URLTest("my_planner_add_object", [], {"job_date": "2024-05-14", "destination": "task", "task": 1}, 200, "POST"), + URLTest("my_planner_delete_user_job", [], {"user_job_id": 1}, 200, "POST"), + URLTest("my_planner_get_object_list", ["project"], {}, 200, "POST"), + URLTest("my_planner_get_object_list", ["task"], {}, 200, "POST"), + URLTest("my_planner_update_object_list", [], {"user_job_id": 1, "job_date": "2024-05-15", "new_destination": 1}, 200, "POST"), + URLTest("gantt_chart_get_data", ["sprint", 3], {}, 200, "GET"), + URLTest("gantt_chart_get_data", ["project", 3], {}, 400, "GET"), # URLTest("diagnostic_information_upload_test", [], {}, 200, "POST"), ] diff --git a/NearBeach/tests/tests_user_permissions/test_redirection_of_non_logged_in_users.py b/NearBeach/tests/tests_user_permissions/test_redirection_of_non_logged_in_users.py index ff83ae314..9c3445abc 100644 --- a/NearBeach/tests/tests_user_permissions/test_redirection_of_non_logged_in_users.py +++ b/NearBeach/tests/tests_user_permissions/test_redirection_of_non_logged_in_users.py @@ -60,6 +60,7 @@ def test_dashboard(self): c.get(reverse("dashboard")), c.get(reverse("get_bug_list")), c.get(reverse("get_my_objects")), + c.get(reverse("my_planner")), ] # Check the data in the array @@ -232,6 +233,11 @@ def test_objects_post(self): c.get(reverse("update_note", args=[1]), data={"object_note_id": 1, "object_note": ""}), c.post(reverse("user_list", args=["project", 1])), c.post(reverse("lead_user_list")), + c.post(reverse("my_planner_add_object")), + c.post(reverse("my_planner_delete_user_job")), + c.post(reverse("my_planner_get_object_list", args=["project"])), + c.post(reverse("my_planner_get_object_list", args=["task"])), + c.post(reverse("my_planner_update_object_list")), ] # Check the array diff --git a/NearBeach/tests/tests_user_permissions/test_team_leader.py b/NearBeach/tests/tests_user_permissions/test_team_leader.py index 8b981b217..0336ba6a8 100644 --- a/NearBeach/tests/tests_user_permissions/test_team_leader.py +++ b/NearBeach/tests/tests_user_permissions/test_team_leader.py @@ -82,6 +82,14 @@ def test_basic_page_loads_successful(self): URLTest("user_information", [1], {}, 403), URLTest("add_customer", ["project", 2], {"customer": 1}, 200, "POST"), URLTest("private_download_file", ["80a7bd50-eba9-49f8-a55c-d1febd052ab9"], {}, 400), + URLTest("my_planner", [], {}, 200, "GET"), + URLTest("my_planner_add_object", [], {"job_date": "2024-05-14", "destination": "task", "task": 1}, 200, "POST"), + URLTest("my_planner_delete_user_job", [], {"user_job_id": 1}, 400, "POST"), + URLTest("my_planner_delete_user_job", [], {"user_job_id": 3}, 200, "POST"), + URLTest("my_planner_get_object_list", ["project"], {}, 200, "POST"), + URLTest("my_planner_get_object_list", ["task"], {}, 200, "POST"), + URLTest("my_planner_update_object_list", [], {"user_job_id": 1, "job_date": "2024-05-15", "new_destination": 1}, 400, "POST"), + URLTest("my_planner_update_object_list", [], {"user_job_id": 3, "job_date": "2024-05-15", "new_destination": 3}, 200, "POST"), ] # Loop through each url to test to make sure the decorator is applied diff --git a/NearBeach/views/user_job_views.py b/NearBeach/views/user_job_views.py index 84a43caf0..ea1a58d48 100644 --- a/NearBeach/views/user_job_views.py +++ b/NearBeach/views/user_job_views.py @@ -1,7 +1,9 @@ +from django.contrib.auth.decorators import login_required from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse from django.template import loader from django.db.models import F, Value as V from django.core.serializers.json import DjangoJSONEncoder +from django.views.decorators.http import require_http_methods from NearBeach.forms import MyPlannerAddObjectForm, MyPlannerUpdateObjectListForm, MyPlannerDeleteUserJobForm from NearBeach.models import KanbanCard, ObjectAssignment, Project, Task, UserJob @@ -43,6 +45,7 @@ } +# Internal Function def get_my_planning_objects(request, delta=7): # Lowest delta is a 1 if delta < 1: @@ -144,6 +147,7 @@ def get_my_planning_objects(request, delta=7): return json.dumps(list(results), cls=DjangoJSONEncoder) +@login_required(login_url="login", redirect_field_name="") def my_planner(request): # Template t = loader.get_template("NearBeach/my_planner/my_planner.html") @@ -160,6 +164,8 @@ def my_planner(request): return HttpResponse(t.render(c, request)) +@login_required(login_url="login", redirect_field_name="") +@require_http_methods(["POST"]) def my_planner_add_object(request): form = MyPlannerAddObjectForm(request.POST) if not form.is_valid(): @@ -208,6 +214,8 @@ def my_planner_add_object(request): return JsonResponse(json.loads(results), safe=False) +@login_required(login_url="login", redirect_field_name="") +@require_http_methods(["POST"]) def my_planner_delete_user_job(request): form = MyPlannerDeleteUserJobForm(request.POST) if not form.is_valid(): @@ -215,12 +223,19 @@ def my_planner_delete_user_job(request): # Delete the data user_job_update = form.cleaned_data["user_job_id"] + + # Check permissions. Username should match current logged in user + if not user_job_update.username == request.user: + return HttpResponseBadRequest("Can't modify other users") + user_job_update.is_deleted = True user_job_update.save() return HttpResponse() +@login_required(login_url="login", redirect_field_name="") +@require_http_methods(["POST"]) def my_planner_get_object_list(request, destination): # Make sure the destination is correct if destination not in ["kanban_card", "project", "task"]: @@ -264,6 +279,8 @@ def my_planner_get_object_list(request, destination): return JsonResponse(json.loads(results), safe=False) +@login_required(login_url="login", redirect_field_name="") +@require_http_methods(["POST"]) def my_planner_update_object_list(request): form = MyPlannerUpdateObjectListForm(request.POST) if not form.is_valid(): @@ -273,6 +290,10 @@ def my_planner_update_object_list(request): user_job_update = form.cleaned_data["user_job_id"] user_job_update.job_date = form.cleaned_data["job_date"] + # Check to make sure the user job username is the request username + if not user_job_update.username == request.user: + return HttpResponseBadRequest("Can't modify other users") + # Update the sort order. # old_destination is optional new_destination = form.cleaned_data['new_destination'] From a36fce1311a7c7a3af79395793329fbc45f82184 Mon Sep 17 00:00:00 2001 From: nearbeach Date: Mon, 13 May 2024 21:47:53 +1000 Subject: [PATCH 2/7] Implementation of unit tests for missing front end components --- .../diagnostics/DiagnosticEmailTest.unit.js | 22 +++++++++++++ .../diagnostics/DiagnosticInformation.unit.js | 22 +++++++++++++ .../diagnostics/DiagnosticUploadTest.unit.js | 22 +++++++++++++ .../unit/gantt_chart/GanttInformation.unit.js | 22 +++++++++++++ .../gantt_chart/RenderGanttDaysHeader.unit.js | 22 +++++++++++++ .../RenderGanttMonthlyHeader.unit.js | 22 +++++++++++++ tests/unit/gantt_chart/RenderGanttRow.unit.js | 22 +++++++++++++ .../my_planner/ConfirmUserJobDelete.unit.js | 27 ++++++++++++++++ tests/unit/my_planner/MyPlanner.unit.js | 27 ++++++++++++++++ .../my_planner/NewPlannerObjectWizard.unit.js | 27 ++++++++++++++++ .../notifications/NewNotifications.unit.js | 27 ++++++++++++++++ .../NotificationInformation.unit.js | 32 +++++++++++++++++++ .../ObjectStatusConfirmDelete.unit.js | 27 ++++++++++++++++ .../ObjectStatusInformation.unit.js | 27 ++++++++++++++++ .../object_status/ObjectStatusList.unit.js | 27 ++++++++++++++++ .../object_status/ObjectStatusModal.unit.js | 27 ++++++++++++++++ tests/unit/sprint/AddObjectWizard.unit.js | 27 ++++++++++++++++ tests/unit/sprint/ConfirmSprintDelete.unit.js | 27 ++++++++++++++++ tests/unit/sprint/SprintInformation.unit.js | 32 +++++++++++++++++++ 19 files changed, 488 insertions(+) create mode 100644 tests/unit/diagnostics/DiagnosticEmailTest.unit.js create mode 100644 tests/unit/diagnostics/DiagnosticInformation.unit.js create mode 100644 tests/unit/diagnostics/DiagnosticUploadTest.unit.js create mode 100644 tests/unit/gantt_chart/GanttInformation.unit.js create mode 100644 tests/unit/gantt_chart/RenderGanttDaysHeader.unit.js create mode 100644 tests/unit/gantt_chart/RenderGanttMonthlyHeader.unit.js create mode 100644 tests/unit/gantt_chart/RenderGanttRow.unit.js create mode 100644 tests/unit/my_planner/ConfirmUserJobDelete.unit.js create mode 100644 tests/unit/my_planner/MyPlanner.unit.js create mode 100644 tests/unit/my_planner/NewPlannerObjectWizard.unit.js create mode 100644 tests/unit/notifications/NewNotifications.unit.js create mode 100644 tests/unit/notifications/NotificationInformation.unit.js create mode 100644 tests/unit/object_status/ObjectStatusConfirmDelete.unit.js create mode 100644 tests/unit/object_status/ObjectStatusInformation.unit.js create mode 100644 tests/unit/object_status/ObjectStatusList.unit.js create mode 100644 tests/unit/object_status/ObjectStatusModal.unit.js create mode 100644 tests/unit/sprint/AddObjectWizard.unit.js create mode 100644 tests/unit/sprint/ConfirmSprintDelete.unit.js create mode 100644 tests/unit/sprint/SprintInformation.unit.js diff --git a/tests/unit/diagnostics/DiagnosticEmailTest.unit.js b/tests/unit/diagnostics/DiagnosticEmailTest.unit.js new file mode 100644 index 000000000..82a96f3df --- /dev/null +++ b/tests/unit/diagnostics/DiagnosticEmailTest.unit.js @@ -0,0 +1,22 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import BetweenDates from "/src/js/components/diagnostic/DiagnosticEmailTest.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +describe('DiagnosticEmailTest.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(BetweenDates, { + props: { + }, + global: { + plugins: [store], + }, + }); + + test('Empty test', () => {}); +}); diff --git a/tests/unit/diagnostics/DiagnosticInformation.unit.js b/tests/unit/diagnostics/DiagnosticInformation.unit.js new file mode 100644 index 000000000..2d74d4dc5 --- /dev/null +++ b/tests/unit/diagnostics/DiagnosticInformation.unit.js @@ -0,0 +1,22 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import BetweenDates from "/src/js/components/diagnostic/DiagnosticInformation.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +describe('DiagnosticInformation.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(BetweenDates, { + props: { + }, + global: { + plugins: [store], + }, + }); + + test('Empty test', () => {}); +}); diff --git a/tests/unit/diagnostics/DiagnosticUploadTest.unit.js b/tests/unit/diagnostics/DiagnosticUploadTest.unit.js new file mode 100644 index 000000000..1cbe2eb48 --- /dev/null +++ b/tests/unit/diagnostics/DiagnosticUploadTest.unit.js @@ -0,0 +1,22 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import BetweenDates from "/src/js/components/diagnostic/DiagnosticUploadTest.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +describe('DiagnosticUploadTest.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(BetweenDates, { + props: { + }, + global: { + plugins: [store], + }, + }); + + test('Empty test', () => {}); +}); diff --git a/tests/unit/gantt_chart/GanttInformation.unit.js b/tests/unit/gantt_chart/GanttInformation.unit.js new file mode 100644 index 000000000..208ab8e2c --- /dev/null +++ b/tests/unit/gantt_chart/GanttInformation.unit.js @@ -0,0 +1,22 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import BetweenDates from "/src/js/components/gantt_chart/GanttInformation.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +describe('GanttInformation.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(BetweenDates, { + props: { + }, + global: { + plugins: [store], + }, + }); + + test('Empty test', () => {}); +}); diff --git a/tests/unit/gantt_chart/RenderGanttDaysHeader.unit.js b/tests/unit/gantt_chart/RenderGanttDaysHeader.unit.js new file mode 100644 index 000000000..c4c4f0afc --- /dev/null +++ b/tests/unit/gantt_chart/RenderGanttDaysHeader.unit.js @@ -0,0 +1,22 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import BetweenDates from "/src/js/components/gantt_chart/RenderGanttDaysHeader.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +describe('RenderGanttDaysHeader.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(BetweenDates, { + props: { + }, + global: { + plugins: [store], + }, + }); + + test('Empty test', () => {}); +}); diff --git a/tests/unit/gantt_chart/RenderGanttMonthlyHeader.unit.js b/tests/unit/gantt_chart/RenderGanttMonthlyHeader.unit.js new file mode 100644 index 000000000..b1446e09a --- /dev/null +++ b/tests/unit/gantt_chart/RenderGanttMonthlyHeader.unit.js @@ -0,0 +1,22 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import BetweenDates from "/src/js/components/gantt_chart/RenderGanttMonthlyHeader.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +describe('RenderGanttMonthlyHeader.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(BetweenDates, { + props: { + }, + global: { + plugins: [store], + }, + }); + + test('Empty test', () => {}); +}); diff --git a/tests/unit/gantt_chart/RenderGanttRow.unit.js b/tests/unit/gantt_chart/RenderGanttRow.unit.js new file mode 100644 index 000000000..54f7c1d54 --- /dev/null +++ b/tests/unit/gantt_chart/RenderGanttRow.unit.js @@ -0,0 +1,22 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import BetweenDates from "/src/js/components/gantt_chart/RenderGanttRow.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +describe('RenderGanttRow.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(BetweenDates, { + props: { + }, + global: { + plugins: [store], + }, + }); + + test('Empty test', () => {}); +}); diff --git a/tests/unit/my_planner/ConfirmUserJobDelete.unit.js b/tests/unit/my_planner/ConfirmUserJobDelete.unit.js new file mode 100644 index 000000000..ef6d235b7 --- /dev/null +++ b/tests/unit/my_planner/ConfirmUserJobDelete.unit.js @@ -0,0 +1,27 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import ParentModules from "/src/js/components/my_planner/ConfirmUserJobDelete.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +// Axios +const axios = require("axios"); + +describe(' ConfirmUserJobDelete.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(ParentModules, { + props: {}, + global: { + plugins: [store], + mocks: { + axios, + } + }, + }); + + test('Empty test', () => {}); +}) \ No newline at end of file diff --git a/tests/unit/my_planner/MyPlanner.unit.js b/tests/unit/my_planner/MyPlanner.unit.js new file mode 100644 index 000000000..ff7c845e9 --- /dev/null +++ b/tests/unit/my_planner/MyPlanner.unit.js @@ -0,0 +1,27 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import ParentModules from "/src/js/components/my_planner/MyPlanner.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +// Axios +const axios = require("axios"); + +describe(' MyPlanner.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(ParentModules, { + props: {}, + global: { + plugins: [store], + mocks: { + axios, + } + }, + }); + + test('Empty test', () => {}); +}) \ No newline at end of file diff --git a/tests/unit/my_planner/NewPlannerObjectWizard.unit.js b/tests/unit/my_planner/NewPlannerObjectWizard.unit.js new file mode 100644 index 000000000..8d3fc331b --- /dev/null +++ b/tests/unit/my_planner/NewPlannerObjectWizard.unit.js @@ -0,0 +1,27 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import ParentModules from "/src/js/components/my_planner/NewPlannerObjectWizard.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +// Axios +const axios = require("axios"); + +describe(' NewPlannerObjectWizard.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(ParentModules, { + props: {}, + global: { + plugins: [store], + mocks: { + axios, + } + }, + }); + + test('Empty test', () => {}); +}) \ No newline at end of file diff --git a/tests/unit/notifications/NewNotifications.unit.js b/tests/unit/notifications/NewNotifications.unit.js new file mode 100644 index 000000000..5257bfaf7 --- /dev/null +++ b/tests/unit/notifications/NewNotifications.unit.js @@ -0,0 +1,27 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import ParentModules from "/src/js/components/notifications/NewNotification.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +// Axios +const axios = require("axios"); + +describe(' NewNotification.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(ParentModules, { + props: {}, + global: { + plugins: [store], + mocks: { + axios, + } + }, + }); + + test('Empty test', () => {}); +}) \ No newline at end of file diff --git a/tests/unit/notifications/NotificationInformation.unit.js b/tests/unit/notifications/NotificationInformation.unit.js new file mode 100644 index 000000000..766d4a810 --- /dev/null +++ b/tests/unit/notifications/NotificationInformation.unit.js @@ -0,0 +1,32 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import ParentModules from "/src/js/components/notifications/NotificationInformation.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +// Axios +const axios = require("axios"); + +describe(' NotificationInformation.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(ParentModules, { + props: { + notificationResults: [{"model":"NearBeach.notification","pk":1,"fields":{"notification_header":"A simple notification for everyone","notification_message":"Please ignore - this is a fixture notification","notification_start_date":"2023-11-14T22:00:00Z","notification_end_date":"2099-11-27T05:00:00Z","notification_location":"all","date_created":"2023-11-15T11:05:26.869Z","date_modified":"2023-11-15T11:05:26.869Z","change_user":null,"is_deleted":false}}], + rootUrl: "/", + staticUrl: "/static/", + theme: "light", + }, + global: { + plugins: [store], + mocks: { + axios, + } + }, + }); + + test('Empty test', () => {}); +}) \ No newline at end of file diff --git a/tests/unit/object_status/ObjectStatusConfirmDelete.unit.js b/tests/unit/object_status/ObjectStatusConfirmDelete.unit.js new file mode 100644 index 000000000..dfcf3fac4 --- /dev/null +++ b/tests/unit/object_status/ObjectStatusConfirmDelete.unit.js @@ -0,0 +1,27 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import ParentModules from "/src/js/components/object_status/ObjectStatusConfirmDelete.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +// Axios +const axios = require("axios"); + +describe(' ObjectStatusConfirmDelete.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(ParentModules, { + props: {}, + global: { + plugins: [store], + mocks: { + axios, + } + }, + }); + + test('Empty test', () => {}); +}) \ No newline at end of file diff --git a/tests/unit/object_status/ObjectStatusInformation.unit.js b/tests/unit/object_status/ObjectStatusInformation.unit.js new file mode 100644 index 000000000..6b4450531 --- /dev/null +++ b/tests/unit/object_status/ObjectStatusInformation.unit.js @@ -0,0 +1,27 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import ParentModules from "/src/js/components/object_status/ObjectStatusInformation.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +// Axios +const axios = require("axios"); + +describe(' ObjectStatusInformation.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(ParentModules, { + props: {}, + global: { + plugins: [store], + mocks: { + axios, + } + }, + }); + + test('Empty test', () => {}); +}) \ No newline at end of file diff --git a/tests/unit/object_status/ObjectStatusList.unit.js b/tests/unit/object_status/ObjectStatusList.unit.js new file mode 100644 index 000000000..e48064efd --- /dev/null +++ b/tests/unit/object_status/ObjectStatusList.unit.js @@ -0,0 +1,27 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import ParentModules from "/src/js/components/object_status/ObjectStatusList.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +// Axios +const axios = require("axios"); + +describe(' ObjectStatusList.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(ParentModules, { + props: {}, + global: { + plugins: [store], + mocks: { + axios, + } + }, + }); + + test('Empty test', () => {}); +}) \ No newline at end of file diff --git a/tests/unit/object_status/ObjectStatusModal.unit.js b/tests/unit/object_status/ObjectStatusModal.unit.js new file mode 100644 index 000000000..99c1e0227 --- /dev/null +++ b/tests/unit/object_status/ObjectStatusModal.unit.js @@ -0,0 +1,27 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import ParentModules from "/src/js/components/object_status/ObjectStatusModal.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +// Axios +const axios = require("axios"); + +describe(' ObjectStatusModal.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(ParentModules, { + props: {}, + global: { + plugins: [store], + mocks: { + axios, + } + }, + }); + + test('Empty test', () => {}); +}) \ No newline at end of file diff --git a/tests/unit/sprint/AddObjectWizard.unit.js b/tests/unit/sprint/AddObjectWizard.unit.js new file mode 100644 index 000000000..8513e2ec6 --- /dev/null +++ b/tests/unit/sprint/AddObjectWizard.unit.js @@ -0,0 +1,27 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import ParentModules from "/src/js/components/sprints/AddObjectWizard.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +// Axios +const axios = require("axios"); + +describe(' AddObjectWizard.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(ParentModules, { + props: {}, + global: { + plugins: [store], + mocks: { + axios, + } + }, + }); + + test('Empty test', () => {}); +}) \ No newline at end of file diff --git a/tests/unit/sprint/ConfirmSprintDelete.unit.js b/tests/unit/sprint/ConfirmSprintDelete.unit.js new file mode 100644 index 000000000..acee2ffb2 --- /dev/null +++ b/tests/unit/sprint/ConfirmSprintDelete.unit.js @@ -0,0 +1,27 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import ParentModules from "/src/js/components/sprints/ConfirmSprintDelete.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +// Axios +const axios = require("axios"); + +describe(' ConfirmSprintDelete.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(ParentModules, { + props: {}, + global: { + plugins: [store], + mocks: { + axios, + } + }, + }); + + test('Empty test', () => {}); +}) \ No newline at end of file diff --git a/tests/unit/sprint/SprintInformation.unit.js b/tests/unit/sprint/SprintInformation.unit.js new file mode 100644 index 000000000..119cf15b8 --- /dev/null +++ b/tests/unit/sprint/SprintInformation.unit.js @@ -0,0 +1,32 @@ +// Vitest +import { describe, expect, test } from "vitest"; +import {mount, VueWrapper} from "@vue/test-utils"; + +// Import vue component +import ParentModules from "/src/js/components/sprints/SprintInformation.vue"; + +// VueX +import { store } from "/src/js/vuex-store"; + +// Axios +const axios = require("axios"); + +describe(' SprintInformation.vue - rendering component', () => { + //Using mount - insert data + const wrapper = mount(ParentModules, { + props: { + rootUrl: "/", + sprintResults: [{"sprint_id":2,"completed_story_points":0,"project":2,"requirement":null,"sprint_name":"project-2 - Sprint 1 - 18 Mar 2024","sprint_end_date":"2024-03-25T08:45:29.300Z","sprint_start_date":"2024-03-18T08:45:29.300Z","sprint_status":"Draft","total_story_points":0}], + theme: "light", + userLevel: 4, + }, + global: { + plugins: [store], + mocks: { + axios, + } + }, + }); + + test('Empty test', () => {}); +}) \ No newline at end of file From a5781720a0ff76f25a9684521af0592ef10f7c89 Mon Sep 17 00:00:00 2001 From: nearbeach Date: Wed, 15 May 2024 18:59:01 +1000 Subject: [PATCH 3/7] Bugfix - nearbeach-1354 Customer Information django template - still using fieldset --- src/js/components/customers/CustomerInformation.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/js/components/customers/CustomerInformation.vue b/src/js/components/customers/CustomerInformation.vue index 981d0c52a..fbc01c5c7 100644 --- a/src/js/components/customers/CustomerInformation.vue +++ b/src/js/components/customers/CustomerInformation.vue @@ -54,6 +54,7 @@ @@ -68,6 +69,7 @@ type="text" class="form-control" v-model="customerFirstNameModel" + :disabled="userLevel <= 1" />
@@ -81,6 +83,7 @@ type="text" class="form-control" v-model="customerLastNameModel" + :disabled="userLevel <= 1" />
From 2bf018b3419dae0e1afca35f4487f55f2bc47da4 Mon Sep 17 00:00:00 2001 From: nearbeach Date: Wed, 15 May 2024 20:44:27 +1000 Subject: [PATCH 4/7] Bugfix - nearbeach-1380 Gantt Charts can get around security --- .../gantt_chart_permissions.py | 71 +++++++++++++++++++ .../partials/requirement_permissions.py | 15 ++-- .../customers/customer_information.html | 7 -- .../tests_user_permissions/test_admin_user.py | 2 - .../test_team_leader.py | 9 +++ NearBeach/views/gantt_chart_views.py | 11 +-- 6 files changed, 91 insertions(+), 24 deletions(-) create mode 100644 NearBeach/decorators/check_user_permissions/gantt_chart_permissions.py diff --git a/NearBeach/decorators/check_user_permissions/gantt_chart_permissions.py b/NearBeach/decorators/check_user_permissions/gantt_chart_permissions.py new file mode 100644 index 000000000..765c72e0e --- /dev/null +++ b/NearBeach/decorators/check_user_permissions/gantt_chart_permissions.py @@ -0,0 +1,71 @@ +from django.core.exceptions import PermissionDenied +from django.db.models import Max +from functools import wraps + +from NearBeach.models import Sprint + +from NearBeach.decorators.check_user_permissions.object_permissions import FUNCTION_DICT + + +def check_gantt_chart_permissions_with_destination(min_permission_level): + """ + Checks the user's permission for the gantt chart, using the destination as + the object lookup. We will need to make sure the destination is either + a project or requirement, otherwise they will get permission denied. + We will then utilise the correct permission partials to determine if the + user should have access. + """ + def decorator(func): + @wraps(func) + def inner(request, *args, **kwargs): + # Obtain destination from args + # due to weird issue - we check the args length + if len(args) == 0: + destination = kwargs["destination"] + location_id = kwargs["location_id"] + else: + destination = args[0] + location_id = args[1] + + # TEMP FUNCTIONALITY -> At the moment, we are only focusing on sprint functionality + # In the near future, we'll open this up to OTHER objects + # For the initial proof of concept, we are only dealing with sprints + if not destination == "sprint": + raise PermissionDenied + + destination, location_id = get_sprint_parent_destination(location_id) + + # If user is admin - grant them all permissions + if request.user.is_superuser: + # Return the function with a user_level of 4 + return func(request, *args, **kwargs, user_level=4) + + # Fold the location id into the {destination}_id. This will be used in the partials + kwargs[F"{destination}_id"] = location_id + + # User the FUNCTION_DICT to determine which partial permissions we need + # to reference + passes, user_level = FUNCTION_DICT[destination](request, kwargs) + + if not passes: + raise PermissionDenied + + if user_level >= min_permission_level: + # Everything is fine - continue on + return func(request, *args, **kwargs, user_level=user_level) + + # Does not meet conditions + raise PermissionDenied + return inner + return decorator + + +def get_sprint_parent_destination(location_id): + # Get the sprint information + sprint_results = Sprint.objects.get(sprint_id=location_id) + + # If there is a project assigned to the sprint. The destination should be project + if sprint_results.project is not None: + return "project", sprint_results.project_id + else: + return "requirement", sprint_results.requirement_id diff --git a/NearBeach/decorators/check_user_permissions/partials/requirement_permissions.py b/NearBeach/decorators/check_user_permissions/partials/requirement_permissions.py index 2ddfedd4b..781b5c226 100644 --- a/NearBeach/decorators/check_user_permissions/partials/requirement_permissions.py +++ b/NearBeach/decorators/check_user_permissions/partials/requirement_permissions.py @@ -12,17 +12,12 @@ def requirement_permissions(request, kwargs): if len(kwargs) > 0: # Get the requirement groups user_group_results = user_group_results.filter( - Q( + is_deleted=False, + group_id__in=ObjectAssignment.objects.filter( is_deleted=False, - group_id__in=ObjectAssignment.objects.filter( - is_deleted=False, - group_id__isnull=False, - requirement_id=kwargs["requirement_id"], - ).values("group_id"), - ) - & Q( - username=request.user, - ) + group_id__isnull=False, + requirement_id=kwargs["requirement_id"], + ).values("group_id"), ) # Check to see if there are any groups associated diff --git a/NearBeach/templates/NearBeach/customers/customer_information.html b/NearBeach/templates/NearBeach/customers/customer_information.html index 914fcf52a..da6641a18 100644 --- a/NearBeach/templates/NearBeach/customers/customer_information.html +++ b/NearBeach/templates/NearBeach/customers/customer_information.html @@ -2,10 +2,6 @@ {% load static %} {% block content %} - {% if user_level == 1 %} -
- {% endif %} - - {% if user_level == 1 %} -
- {% endif %} {% endblock %} diff --git a/NearBeach/tests/tests_user_permissions/test_admin_user.py b/NearBeach/tests/tests_user_permissions/test_admin_user.py index e9f2e87e8..b184fa774 100644 --- a/NearBeach/tests/tests_user_permissions/test_admin_user.py +++ b/NearBeach/tests/tests_user_permissions/test_admin_user.py @@ -112,8 +112,6 @@ def test_basic_permissions_as_admin(self): URLTest("my_planner_get_object_list", ["project"], {}, 200, "POST"), URLTest("my_planner_get_object_list", ["task"], {}, 200, "POST"), URLTest("my_planner_update_object_list", [], {"user_job_id": 1, "job_date": "2024-05-15", "new_destination": 1}, 200, "POST"), - URLTest("gantt_chart_get_data", ["sprint", 3], {}, 200, "GET"), - URLTest("gantt_chart_get_data", ["project", 3], {}, 400, "GET"), # URLTest("diagnostic_information_upload_test", [], {}, 200, "POST"), ] diff --git a/NearBeach/tests/tests_user_permissions/test_team_leader.py b/NearBeach/tests/tests_user_permissions/test_team_leader.py index 0336ba6a8..72676ae58 100644 --- a/NearBeach/tests/tests_user_permissions/test_team_leader.py +++ b/NearBeach/tests/tests_user_permissions/test_team_leader.py @@ -90,6 +90,15 @@ def test_basic_page_loads_successful(self): URLTest("my_planner_get_object_list", ["task"], {}, 200, "POST"), URLTest("my_planner_update_object_list", [], {"user_job_id": 1, "job_date": "2024-05-15", "new_destination": 1}, 400, "POST"), URLTest("my_planner_update_object_list", [], {"user_job_id": 3, "job_date": "2024-05-15", "new_destination": 3}, 200, "POST"), + URLTest("sprint_information", [1], {}, 403, "GET"), + URLTest("sprint_information", [2], {}, 200, "GET"), + URLTest("sprint_information", [3], {}, 403, "GET"), + URLTest("sprint_information", [4], {}, 200, "GET"), + URLTest("gantt_chart_get_data", ["sprint", 1], {}, 403, "GET"), + URLTest("gantt_chart_get_data", ["sprint", 2], {}, 200, "GET"), + URLTest("gantt_chart_get_data", ["sprint", 3], {}, 403, "GET"), + URLTest("gantt_chart_get_data", ["sprint", 4], {}, 200, "GET"), + URLTest("gantt_chart_get_data", ["project", 3], {}, 403, "GET"), ] # Loop through each url to test to make sure the decorator is applied diff --git a/NearBeach/views/gantt_chart_views.py b/NearBeach/views/gantt_chart_views.py index 0f0ca3b19..dcf63477b 100644 --- a/NearBeach/views/gantt_chart_views.py +++ b/NearBeach/views/gantt_chart_views.py @@ -10,6 +10,9 @@ SprintObjectAssignment, Task, ListOfRequirementItemStatus, ListOfProjectStatus, ListOfTaskStatus, ) +from NearBeach.decorators.check_user_permissions.gantt_chart_permissions import ( + check_gantt_chart_permissions_with_destination, +) import json @@ -31,11 +34,8 @@ } +@check_gantt_chart_permissions_with_destination(min_permission_level=1) def gantt_data_get_data(request, destination, location_id, *args, **kwargs): - # For the initial proof of concept, we are only dealing with sprints - if destination != "sprint": - return HttpResponseBadRequest("Sorry, object not supported at the moment") - # Get the object results object_results = get_object_results(location_id) status_results = get_status_results() @@ -51,7 +51,8 @@ def gantt_data_get_data(request, destination, location_id, *args, **kwargs): ) -def gantt_data_update_data(request, destination, location_id): +@check_gantt_chart_permissions_with_destination(min_permission_level=2) +def gantt_data_update_data(request, destination, location_id, *args, **kwargs): # Check the form data form = GanttDataUpdateDataForm(request.POST) if not form.is_valid(): From efadcc818497c8397e0e9b4fa8526462e5b51407 Mon Sep 17 00:00:00 2001 From: nearbeach Date: Wed, 15 May 2024 21:03:32 +1000 Subject: [PATCH 5/7] Fix to an issue with the gantt chart permissions. Also added in more unit tests --- NearBeach/tests/tests_user_permissions/test_team_leader.py | 4 ++++ NearBeach/views/gantt_chart_views.py | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/NearBeach/tests/tests_user_permissions/test_team_leader.py b/NearBeach/tests/tests_user_permissions/test_team_leader.py index 72676ae58..78a6cf692 100644 --- a/NearBeach/tests/tests_user_permissions/test_team_leader.py +++ b/NearBeach/tests/tests_user_permissions/test_team_leader.py @@ -99,6 +99,10 @@ def test_basic_page_loads_successful(self): URLTest("gantt_chart_get_data", ["sprint", 3], {}, 403, "GET"), URLTest("gantt_chart_get_data", ["sprint", 4], {}, 200, "GET"), URLTest("gantt_chart_get_data", ["project", 3], {}, 403, "GET"), + URLTest("gantt_chart_update_data", ["project", 1], {"end_date": "2024-05-10", "start_date": "2024-05-01", "status_id": 1}, 403, "POST"), + URLTest("gantt_chart_update_data", ["project", 2], {"end_date": "2024-05-10", "start_date": "2024-05-01", "status_id": 1}, 200, "POST"), + URLTest("gantt_chart_update_data", ["task", 1], {"end_date": "2024-05-10", "start_date": "2024-05-01", "status_id": 1}, 403, "POST"), + URLTest("gantt_chart_update_data", ["task", 2], {"end_date": "2024-05-10", "start_date": "2024-05-01", "status_id": 1}, 200, "POST"), ] # Loop through each url to test to make sure the decorator is applied diff --git a/NearBeach/views/gantt_chart_views.py b/NearBeach/views/gantt_chart_views.py index dcf63477b..66ad3ee27 100644 --- a/NearBeach/views/gantt_chart_views.py +++ b/NearBeach/views/gantt_chart_views.py @@ -13,6 +13,9 @@ from NearBeach.decorators.check_user_permissions.gantt_chart_permissions import ( check_gantt_chart_permissions_with_destination, ) +from NearBeach.decorators.check_user_permissions.object_permissions import ( + check_user_generic_permissions, +) import json @@ -51,7 +54,7 @@ def gantt_data_get_data(request, destination, location_id, *args, **kwargs): ) -@check_gantt_chart_permissions_with_destination(min_permission_level=2) +@check_user_generic_permissions(min_permission_level=2) def gantt_data_update_data(request, destination, location_id, *args, **kwargs): # Check the form data form = GanttDataUpdateDataForm(request.POST) From 3679f2115d6c868f761afb15feb648447f878366 Mon Sep 17 00:00:00 2001 From: nearbeach Date: Wed, 15 May 2024 21:29:36 +1000 Subject: [PATCH 6/7] Bugfix - nearbeach-1379 24 hour task - appears as 1 day when spread over two --- src/js/components/gantt_chart/GanttInformation.vue | 4 ++-- src/js/components/gantt_chart/RenderGanttRow.vue | 8 ++++---- src/sass/components/_gantt_chart.scss | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/js/components/gantt_chart/GanttInformation.vue b/src/js/components/gantt_chart/GanttInformation.vue index 657425982..bc088191b 100644 --- a/src/js/components/gantt_chart/GanttInformation.vue +++ b/src/js/components/gantt_chart/GanttInformation.vue @@ -233,8 +233,8 @@ export default { //Deal with the mouse movement const client_x_final = event.clientX; - //Get the number of dates from this - const delta = Math.floor((client_x_final - this.mdClientXInitial) / 35) * (24 * 60 * 60 * 1000); + //Get the number of hours from this + const delta = Math.floor((client_x_final - this.mdClientXInitial) / 2) * (60 * 60 * 1000); diff --git a/src/js/components/gantt_chart/RenderGanttRow.vue b/src/js/components/gantt_chart/RenderGanttRow.vue index 8c8371101..ec2924161 100644 --- a/src/js/components/gantt_chart/RenderGanttRow.vue +++ b/src/js/components/gantt_chart/RenderGanttRow.vue @@ -153,10 +153,10 @@ export default { } //Calculate the delta (aka number of days) - const delta = Math.ceil((end_date - start_date) / (1000 * 60 * 60 * 24)); + const delta = Math.ceil((end_date - start_date) / (1000 * 60 * 60)); //Return number of days multiplied by 35 pixels - return delta * 35; + return delta * 2; }, renderBar() { //Conditions @@ -176,10 +176,10 @@ export default { } //Calculate the delta (aka number of days) - const delta = Math.floor((this.localStartDate - this.startDateGantt) / (1000 * 60 * 60 * 24)); + const delta = Math.floor((this.localStartDate - this.startDateGantt) / (1000 * 60 * 60)); //Return the number of days multiplied by 35 pixels - return delta * 35; + return delta * 2; }, }, methods: { diff --git a/src/sass/components/_gantt_chart.scss b/src/sass/components/_gantt_chart.scss index b027b16f9..748b53694 100644 --- a/src/sass/components/_gantt_chart.scss +++ b/src/sass/components/_gantt_chart.scss @@ -37,7 +37,7 @@ flex-direction: row; .gantt-header--date { - width: 35px; + width: 48px; //background-color: lightgrey; //border: solid 1px dimgray; //color: black; @@ -102,7 +102,7 @@ .gantt-row--bar { display: flex; background-color: var(--nb-kanban-level-div-background-color); - min-width: 35px; + min-width: 24px; height: 20px; margin-top: 10px; border-left: 2px var(--nb-kanban-cell-background-color) dashed; @@ -110,13 +110,13 @@ .gantt-row--bar-start, .gantt-row--bar-end { height: inherit; - width: 5px; + width: 7px; cursor: col-resize; } .gantt-row--bar-middle { height: inherit; - width: calc(100% - 10px); + width: calc(100% - 14px); cursor: grab; } From 1159c9bc7336454a31edb36b8356d5917a949592 Mon Sep 17 00:00:00 2001 From: nearbeach Date: Wed, 15 May 2024 21:46:10 +1000 Subject: [PATCH 7/7] Release 0.31.21 --- NearBeach/static/NearBeach/NearBeach.min.js | 2 +- .../static/NearBeach/NearBeach.min.js.gz | Bin 150238 -> 150279 bytes .../NearBeach/customer-information.min.js | 2 +- .../NearBeach/customer-information.min.js.gz | Bin 3070 -> 3099 bytes .../static/NearBeach/gantt-information.min.js | 2 +- .../NearBeach/gantt-information.min.js.gz | Bin 3472 -> 3475 bytes 6 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NearBeach/static/NearBeach/NearBeach.min.js b/NearBeach/static/NearBeach/NearBeach.min.js index 34bece9d0..baf568993 100644 --- a/NearBeach/static/NearBeach/NearBeach.min.js +++ b/NearBeach/static/NearBeach/NearBeach.min.js @@ -1,2 +1,2 @@ /*! For license information please see NearBeach.min.js.LICENSE.txt */ -(()=>{"use strict";var t,e,r={6795:(t,e,r)=>{r.d(e,{A:()=>o});const o=function(t){for(var e,r=0,o=0,n=t.length;n>=4;++o,n-=4)e=1540483477*(65535&(e=255&t.charCodeAt(o)|(255&t.charCodeAt(++o))<<8|(255&t.charCodeAt(++o))<<16|(255&t.charCodeAt(++o))<<24))+(59797*(e>>>16)<<16),r=1540483477*(65535&(e^=e>>>24))+(59797*(e>>>16)<<16)^1540483477*(65535&r)+(59797*(r>>>16)<<16);switch(n){case 3:r^=(255&t.charCodeAt(o+2))<<16;case 2:r^=(255&t.charCodeAt(o+1))<<8;case 1:r=1540483477*(65535&(r^=255&t.charCodeAt(o)))+(59797*(r>>>16)<<16)}return(((r=1540483477*(65535&(r^=r>>>13))+(59797*(r>>>16)<<16))^r>>>15)>>>0).toString(36)}},8764:(t,e,r)=>{r.r(e),r.d(e,{BASE_TRANSITION:()=>l,BindingTypes:()=>wo,CAMELIZE:()=>L,CAPITALIZE:()=>R,CREATE_BLOCK:()=>d,CREATE_COMMENT:()=>b,CREATE_ELEMENT_BLOCK:()=>p,CREATE_ELEMENT_VNODE:()=>m,CREATE_SLOTS:()=>E,CREATE_STATIC:()=>g,CREATE_TEXT:()=>f,CREATE_VNODE:()=>u,CompilerDeprecationTypes:()=>Et,ConstantTypes:()=>W,DOMDirectiveTransforms:()=>Xo,DOMErrorCodes:()=>Fo,DOMErrorMessages:()=>zo,DOMNodeTransforms:()=>qo,ElementTypes:()=>X,ErrorCodes:()=>Rt,FRAGMENT:()=>n,GUARD_REACTIVE_PROPS:()=>N,IS_MEMO_SAME:()=>U,IS_REF:()=>V,KEEP_ALIVE:()=>s,MERGE_PROPS:()=>T,NORMALIZE_CLASS:()=>O,NORMALIZE_PROPS:()=>C,NORMALIZE_STYLE:()=>A,Namespaces:()=>G,NodeTypes:()=>q,OPEN_BLOCK:()=>c,POP_SCOPE_ID:()=>j,PUSH_SCOPE_ID:()=>P,RENDER_LIST:()=>_,RENDER_SLOT:()=>k,RESOLVE_COMPONENT:()=>h,RESOLVE_DIRECTIVE:()=>y,RESOLVE_DYNAMIC_COMPONENT:()=>v,RESOLVE_FILTER:()=>x,SET_BLOCK_TRACKING:()=>D,SUSPENSE:()=>a,TELEPORT:()=>i,TO_DISPLAY_STRING:()=>S,TO_HANDLERS:()=>I,TO_HANDLER_KEY:()=>M,TRANSITION:()=>Io,TRANSITION_GROUP:()=>Lo,TS_NODE_TYPES:()=>Gt,UNREF:()=>z,V_MODEL_CHECKBOX:()=>Eo,V_MODEL_DYNAMIC:()=>Oo,V_MODEL_RADIO:()=>ko,V_MODEL_SELECT:()=>To,V_MODEL_TEXT:()=>So,V_ON_WITH_KEYS:()=>Co,V_ON_WITH_MODIFIERS:()=>Ao,V_SHOW:()=>No,WITH_CTX:()=>F,WITH_DIRECTIVES:()=>w,WITH_MEMO:()=>$,advancePositionWithClone:()=>oe,advancePositionWithMutation:()=>ne,assert:()=>ie,baseCompile:()=>xo,baseParse:()=>rr,buildDirectiveArgs:()=>Zr,buildProps:()=>Yr,buildSlots:()=>Ur,checkCompatEnabled:()=>At,compile:()=>Wo,convertToBlock:()=>gt,createArrayExpression:()=>Z,createAssignmentExpression:()=>pt,createBlockStatement:()=>lt,createCacheExpression:()=>st,createCallExpression:()=>nt,createCompilerError:()=>Lt,createCompoundExpression:()=>ot,createConditionalExpression:()=>at,createDOMCompilerError:()=>jo,createForLoopParams:()=>jr,createFunctionExpression:()=>it,createIfStatement:()=>dt,createInterpolation:()=>rt,createObjectExpression:()=>Q,createObjectProperty:()=>tt,createReturnStatement:()=>mt,createRoot:()=>K,createSequenceExpression:()=>ut,createSimpleExpression:()=>et,createStructuralDirectiveTransform:()=>fr,createTemplateLiteral:()=>ct,createTransformContext:()=>ur,createVNodeCall:()=>J,errorMessages:()=>Mt,extractIdentifiers:()=>$t,findDir:()=>ae,findProp:()=>se,forAliasRE:()=>we,generate:()=>vr,generateCodeFrame:()=>o.generateCodeFrame,getBaseTransformPreset:()=>yo,getConstantType:()=>ar,getMemoedVNodeCall:()=>xe,getVNodeBlockHelper:()=>ft,getVNodeHelper:()=>bt,hasDynamicKeyVBind:()=>ce,hasScopeRef:()=>ye,helperNameMap:()=>B,injectProp:()=>ge,isCoreComponent:()=>Wt,isFunctionType:()=>Ut,isInDestructureAssignment:()=>jt,isInNewExpression:()=>Ft,isMemberExpression:()=>re,isMemberExpressionBrowser:()=>te,isMemberExpressionNode:()=>ee,isReferencedIdentifier:()=>Pt,isSimpleIdentifier:()=>Kt,isSlotOutlet:()=>me,isStaticArgOf:()=>le,isStaticExp:()=>Xt,isStaticProperty:()=>Bt,isStaticPropertyKey:()=>Ht,isTemplateNode:()=>ue,isText:()=>de,isVSlot:()=>pe,locStub:()=>Y,noopDirectiveTransform:()=>_o,parse:()=>Yo,parserOptions:()=>Mo,processExpression:()=>Or,processFor:()=>Dr,processIf:()=>Nr,processSlotOutlet:()=>eo,registerRuntimeHelpers:()=>H,resolveComponentType:()=>Wr,stringifyExpression:()=>Ar,toValidAssetId:()=>ve,trackSlotScopes:()=>zr,trackVForSlotScopes:()=>Vr,transform:()=>mr,transformBind:()=>no,transformElement:()=>Xr,transformExpression:()=>Tr,transformModel:()=>co,transformOn:()=>oo,transformStyle:()=>Do,traverseNode:()=>br,unwrapTSNode:()=>qt,walkBlockDeclarations:()=>Vt,walkFunctionParams:()=>zt,walkIdentifiers:()=>Dt,warnDeprecation:()=>Ct});var o=r(33);const n=Symbol(""),i=Symbol(""),a=Symbol(""),s=Symbol(""),l=Symbol(""),c=Symbol(""),d=Symbol(""),p=Symbol(""),u=Symbol(""),m=Symbol(""),b=Symbol(""),f=Symbol(""),g=Symbol(""),h=Symbol(""),v=Symbol(""),y=Symbol(""),x=Symbol(""),w=Symbol(""),_=Symbol(""),k=Symbol(""),E=Symbol(""),S=Symbol(""),T=Symbol(""),O=Symbol(""),A=Symbol(""),C=Symbol(""),N=Symbol(""),I=Symbol(""),L=Symbol(""),R=Symbol(""),M=Symbol(""),D=Symbol(""),P=Symbol(""),j=Symbol(""),F=Symbol(""),z=Symbol(""),V=Symbol(""),$=Symbol(""),U=Symbol(""),B={[n]:"Fragment",[i]:"Teleport",[a]:"Suspense",[s]:"KeepAlive",[l]:"BaseTransition",[c]:"openBlock",[d]:"createBlock",[p]:"createElementBlock",[u]:"createVNode",[m]:"createElementVNode",[b]:"createCommentVNode",[f]:"createTextVNode",[g]:"createStaticVNode",[h]:"resolveComponent",[v]:"resolveDynamicComponent",[y]:"resolveDirective",[x]:"resolveFilter",[w]:"withDirectives",[_]:"renderList",[k]:"renderSlot",[E]:"createSlots",[S]:"toDisplayString",[T]:"mergeProps",[O]:"normalizeClass",[A]:"normalizeStyle",[C]:"normalizeProps",[N]:"guardReactiveProps",[I]:"toHandlers",[L]:"camelize",[R]:"capitalize",[M]:"toHandlerKey",[D]:"setBlockTracking",[P]:"pushScopeId",[j]:"popScopeId",[F]:"withCtx",[z]:"unref",[V]:"isRef",[$]:"withMemo",[U]:"isMemoSame"};function H(t){Object.getOwnPropertySymbols(t).forEach((e=>{B[e]=t[e]}))}const G={HTML:0,0:"HTML",SVG:1,1:"SVG",MATH_ML:2,2:"MATH_ML"},q={ROOT:0,0:"ROOT",ELEMENT:1,1:"ELEMENT",TEXT:2,2:"TEXT",COMMENT:3,3:"COMMENT",SIMPLE_EXPRESSION:4,4:"SIMPLE_EXPRESSION",INTERPOLATION:5,5:"INTERPOLATION",ATTRIBUTE:6,6:"ATTRIBUTE",DIRECTIVE:7,7:"DIRECTIVE",COMPOUND_EXPRESSION:8,8:"COMPOUND_EXPRESSION",IF:9,9:"IF",IF_BRANCH:10,10:"IF_BRANCH",FOR:11,11:"FOR",TEXT_CALL:12,12:"TEXT_CALL",VNODE_CALL:13,13:"VNODE_CALL",JS_CALL_EXPRESSION:14,14:"JS_CALL_EXPRESSION",JS_OBJECT_EXPRESSION:15,15:"JS_OBJECT_EXPRESSION",JS_PROPERTY:16,16:"JS_PROPERTY",JS_ARRAY_EXPRESSION:17,17:"JS_ARRAY_EXPRESSION",JS_FUNCTION_EXPRESSION:18,18:"JS_FUNCTION_EXPRESSION",JS_CONDITIONAL_EXPRESSION:19,19:"JS_CONDITIONAL_EXPRESSION",JS_CACHE_EXPRESSION:20,20:"JS_CACHE_EXPRESSION",JS_BLOCK_STATEMENT:21,21:"JS_BLOCK_STATEMENT",JS_TEMPLATE_LITERAL:22,22:"JS_TEMPLATE_LITERAL",JS_IF_STATEMENT:23,23:"JS_IF_STATEMENT",JS_ASSIGNMENT_EXPRESSION:24,24:"JS_ASSIGNMENT_EXPRESSION",JS_SEQUENCE_EXPRESSION:25,25:"JS_SEQUENCE_EXPRESSION",JS_RETURN_STATEMENT:26,26:"JS_RETURN_STATEMENT"},X={ELEMENT:0,0:"ELEMENT",COMPONENT:1,1:"COMPONENT",SLOT:2,2:"SLOT",TEMPLATE:3,3:"TEMPLATE"},W={NOT_CONSTANT:0,0:"NOT_CONSTANT",CAN_SKIP_PATCH:1,1:"CAN_SKIP_PATCH",CAN_HOIST:2,2:"CAN_HOIST",CAN_STRINGIFY:3,3:"CAN_STRINGIFY"},Y={start:{line:1,column:1,offset:0},end:{line:1,column:1,offset:0},source:""};function K(t,e=""){return{type:0,source:e,children:t,helpers:new Set,components:[],directives:[],hoists:[],imports:[],cached:0,temps:0,codegenNode:void 0,loc:Y}}function J(t,e,r,o,n,i,a,s=!1,l=!1,d=!1,p=Y){return t&&(s?(t.helper(c),t.helper(ft(t.inSSR,d))):t.helper(bt(t.inSSR,d)),a&&t.helper(w)),{type:13,tag:e,props:r,children:o,patchFlag:n,dynamicProps:i,directives:a,isBlock:s,disableTracking:l,isComponent:d,loc:p}}function Z(t,e=Y){return{type:17,loc:e,elements:t}}function Q(t,e=Y){return{type:15,loc:e,properties:t}}function tt(t,e){return{type:16,loc:Y,key:(0,o.isString)(t)?et(t,!0):t,value:e}}function et(t,e=!1,r=Y,o=0){return{type:4,loc:r,content:t,isStatic:e,constType:e?3:o}}function rt(t,e){return{type:5,loc:e,content:(0,o.isString)(t)?et(t,!1,e):t}}function ot(t,e=Y){return{type:8,loc:e,children:t}}function nt(t,e=[],r=Y){return{type:14,loc:r,callee:t,arguments:e}}function it(t,e=void 0,r=!1,o=!1,n=Y){return{type:18,params:t,returns:e,newline:r,isSlot:o,loc:n}}function at(t,e,r,o=!0){return{type:19,test:t,consequent:e,alternate:r,newline:o,loc:Y}}function st(t,e,r=!1){return{type:20,index:t,value:e,isVNode:r,loc:Y}}function lt(t){return{type:21,body:t,loc:Y}}function ct(t){return{type:22,elements:t,loc:Y}}function dt(t,e,r){return{type:23,test:t,consequent:e,alternate:r,loc:Y}}function pt(t,e){return{type:24,left:t,right:e,loc:Y}}function ut(t){return{type:25,expressions:t,loc:Y}}function mt(t){return{type:26,returns:t,loc:Y}}function bt(t,e){return t||e?u:m}function ft(t,e){return t||e?d:p}function gt(t,{helper:e,removeHelper:r,inSSR:o}){t.isBlock||(t.isBlock=!0,r(bt(o,t.isComponent)),e(c),e(ft(o,t.isComponent)))}const ht=new Uint8Array([123,123]),vt=new Uint8Array([125,125]);function yt(t){return t>=97&&t<=122||t>=65&&t<=90}function xt(t){return 32===t||10===t||9===t||12===t||13===t}function wt(t){return 47===t||62===t||xt(t)}function _t(t){const e=new Uint8Array(t.length);for(let r=0;r`.sync modifier for v-bind has been removed. Use v-model with argument instead. \`v-bind:${t}.sync\` should be changed to \`v-model:${t}\`.`,link:"https://v3-migration.vuejs.org/breaking-changes/v-model.html"},COMPILER_V_BIND_OBJECT_ORDER:{message:'v-bind="obj" usage is now order sensitive and behaves like JavaScript object spread: it will now overwrite an existing non-mergeable attribute that appears before v-bind in the case of conflict. To retain 2.x behavior, move v-bind to make it the first attribute. You can also suppress this warning if the usage is intended.',link:"https://v3-migration.vuejs.org/breaking-changes/v-bind.html"},COMPILER_V_ON_NATIVE:{message:".native modifier for v-on has been removed as is no longer necessary.",link:"https://v3-migration.vuejs.org/breaking-changes/v-on-native-modifier-removed.html"},COMPILER_V_IF_V_FOR_PRECEDENCE:{message:"v-if / v-for precedence when used on the same element has changed in Vue 3: v-if now takes higher precedence and will no longer have access to v-for scope variables. It is best to avoid the ambiguity with