From d1c3ffffc5f29c1a2ba75171c7833640fd479359 Mon Sep 17 00:00:00 2001 From: jedi Date: Sat, 9 Nov 2024 05:05:05 +0100 Subject: [PATCH 01/16] implement simple backend search for items and tickets --- .forgejo/workflows/deploy_staging.yml | 5 +- core/inventory/tests/v2/test_items.py | 74 +++++++++++++++++++++++++++ core/tickets/serializers.py | 11 ++++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/deploy_staging.yml b/.forgejo/workflows/deploy_staging.yml index 3b44d24..0dccd66 100644 --- a/.forgejo/workflows/deploy_staging.yml +++ b/.forgejo/workflows/deploy_staging.yml @@ -20,9 +20,12 @@ jobs: - name: Run django tests working-directory: core run: python3 manage.py test + - name: Run django coverage + working-directory: core + run: coverage manage.py test deploy: - needs: [test] + needs: [ test ] runs-on: docker steps: - uses: actions/checkout@v4 diff --git a/core/inventory/tests/v2/test_items.py b/core/inventory/tests/v2/test_items.py index 0c85eb4..9b25384 100644 --- a/core/inventory/tests/v2/test_items.py +++ b/core/inventory/tests/v2/test_items.py @@ -13,6 +13,8 @@ from base64 import b64encode from tickets.models import IssueThread, ItemRelation +from base64 import b64encode + class ItemTestCase(TestCase): @@ -343,3 +345,75 @@ class ItemSearchTestCase(TestCase): self.assertEqual('BOX1', response.json()[1]['box']) self.assertEqual(self.box1.id, response.json()[1]['cid']) self.assertEqual(1, response.json()[1]['search_score']) + + + +class ItemSearchTestCase(TestCase): + + def setUp(self): + super().setUp() + self.event = Event.objects.create(slug='EVENT', name='Event') + self.box = Container.objects.create(name='BOX') + self.user = ExtendedUser.objects.create_user('testuser', 'test', 'test') + self.user.user_permissions.add(*Permission.objects.all()) + self.token = AuthToken.objects.create(user=self.user) + self.client = Client(headers={'Authorization': 'Token ' + self.token[1]}) + self.item1 = Item.objects.create(container=self.box, event=self.event, description='abc def') + self.item2 = Item.objects.create(container=self.box, event=self.event, description='def ghi') + self.item3 = Item.objects.create(container=self.box, event=self.event, description='jkl mno pqr') + self.item4 = Item.objects.create(container=self.box, event=self.event, description='stu vwx') + + def test_search(self): + search_query = b64encode(b'abc').decode('utf-8') + response = self.client.get(f'/api/2/{self.event.slug}/items/{search_query}/') + self.assertEqual(200, response.status_code) + self.assertEqual(1, len(response.json())) + self.assertEqual(self.item1.id, response.json()[0]['id']) + self.assertEqual('abc def', response.json()[0]['description']) + self.assertEqual('BOX', response.json()[0]['box']) + self.assertEqual(self.box.id, response.json()[0]['cid']) + self.assertEqual(1, response.json()[0]['search_score']) + + def test_search2(self): + search_query = b64encode(b'def').decode('utf-8') + response = self.client.get(f'/api/2/{self.event.slug}/items/{search_query}/') + self.assertEqual(200, response.status_code) + self.assertEqual(2, len(response.json())) + self.assertEqual(self.item1.id, response.json()[0]['id']) + self.assertEqual('abc def', response.json()[0]['description']) + self.assertEqual('BOX', response.json()[0]['box']) + self.assertEqual(self.box.id, response.json()[0]['cid']) + self.assertEqual(1, response.json()[0]['search_score']) + self.assertEqual(self.item2.id, response.json()[1]['id']) + self.assertEqual('def ghi', response.json()[1]['description']) + self.assertEqual('BOX', response.json()[1]['box']) + self.assertEqual(self.box.id, response.json()[1]['cid']) + self.assertEqual(1, response.json()[0]['search_score']) + + def test_search3(self): + search_query = b64encode(b'jkl').decode('utf-8') + response = self.client.get(f'/api/2/{self.event.slug}/items/{search_query}/') + self.assertEqual(200, response.status_code) + self.assertEqual(1, len(response.json())) + self.assertEqual(self.item3.id, response.json()[0]['id']) + self.assertEqual('jkl mno pqr', response.json()[0]['description']) + self.assertEqual('BOX', response.json()[0]['box']) + self.assertEqual(self.box.id, response.json()[0]['cid']) + self.assertEqual(1, response.json()[0]['search_score']) + + def test_search4(self): + search_query = b64encode(b'abc def').decode('utf-8') + response = self.client.get(f'/api/2/{self.event.slug}/items/{search_query}/') + self.assertEqual(200, response.status_code) + self.assertEqual(2, len(response.json())) + self.assertEqual(self.item1.id, response.json()[0]['id']) + self.assertEqual('abc def', response.json()[0]['description']) + self.assertEqual('BOX', response.json()[0]['box']) + self.assertEqual(self.box.id, response.json()[0]['cid']) + self.assertEqual(2, response.json()[0]['search_score']) + self.assertEqual(self.item2.id, response.json()[1]['id']) + self.assertEqual('def ghi', response.json()[1]['description']) + self.assertEqual('BOX', response.json()[1]['box']) + self.assertEqual(self.box.id, response.json()[1]['cid']) + self.assertEqual(1, response.json()[1]['search_score']) + diff --git a/core/tickets/serializers.py b/core/tickets/serializers.py index 50cdb72..6ce73be 100644 --- a/core/tickets/serializers.py +++ b/core/tickets/serializers.py @@ -146,3 +146,14 @@ class SearchResultSerializer(serializers.Serializer): class Meta: model = IssueThread + + +class SearchResultSerializer(serializers.Serializer): + search_score = serializers.IntegerField() + item = IssueSerializer() + + def to_representation(self, instance): + return {**IssueSerializer(instance['item']).data, 'search_score': instance['search_score']} + + class Meta: + model = IssueThread From 9141752fdfcb39094293c255062c63be42200751 Mon Sep 17 00:00:00 2001 From: jedi Date: Tue, 17 Dec 2024 21:52:57 +0100 Subject: [PATCH 02/16] fix container selection in AddItemModal.vue --- web/src/components/AddItemModal.vue | 55 ++++++++++++++++++------ web/src/components/AddTicketModal.vue | 3 +- web/src/components/EditItem.vue | 50 --------------------- web/src/components/inputs/InputCombo.vue | 6 +-- web/src/views/Item.vue | 2 - web/src/views/Items.vue | 5 +-- 6 files changed, 48 insertions(+), 73 deletions(-) delete mode 100644 web/src/components/EditItem.vue diff --git a/web/src/components/AddItemModal.vue b/web/src/components/AddItemModal.vue index a3c23fd..24bd449 100644 --- a/web/src/components/AddItemModal.vue +++ b/web/src/components/AddItemModal.vue @@ -2,7 +2,29 @@
diff --git a/web/src/components/AddTicketModal.vue b/web/src/components/AddTicketModal.vue index 37d539c..b407670 100644 --- a/web/src/components/AddTicketModal.vue +++ b/web/src/components/AddTicketModal.vue @@ -19,11 +19,10 @@ diff --git a/web/src/components/inputs/InputCombo.vue b/web/src/components/inputs/InputCombo.vue index fc64d42..2a291e0 100644 --- a/web/src/components/inputs/InputCombo.vue +++ b/web/src/components/inputs/InputCombo.vue @@ -43,11 +43,11 @@ export default { props: ['label', 'model', 'nameKey', 'uniqueKey', 'options', 'onOptionAdd'], data: ({options, model, nameKey, uniqueKey}) => ({ internalName: model[nameKey], - selectedOption: options.filter(e => e[uniqueKey] == model[uniqueKey])[0], + selectedOption: options.filter(e => e[uniqueKey] === model[uniqueKey])[0], addingOption: false }), computed: { - isValid: ({options, nameKey, internalName}) => options.some(e => e[nameKey] == internalName), + isValid: ({options, nameKey, internalName}) => options.some(e => e[nameKey] === internalName), sortedOptions: ({ options, nameKey @@ -56,7 +56,7 @@ export default { watch: { internalName(newValue) { if (this.isValid) { - if (!this.selectedOption || newValue != this.selectedOption[this.nameKey]) { + if (!this.selectedOption || newValue !== this.selectedOption[this.nameKey]) { this.selectedOption = this.options.filter(e => e[this.nameKey] === newValue)[0]; } this.model[this.nameKey] = this.selectedOption[this.nameKey]; diff --git a/web/src/views/Item.vue b/web/src/views/Item.vue index d00b8b1..7b9f484 100644 --- a/web/src/views/Item.vue +++ b/web/src/views/Item.vue @@ -112,14 +112,12 @@ import InputString from "@/components/inputs/InputString.vue"; import AuthenticatedImage from "@/components/AuthenticatedImage.vue"; import InputPhoto from "@/components/inputs/InputPhoto.vue"; import Modal from "@/components/Modal.vue"; -import EditItem from "@/components/EditItem.vue"; import AsyncButton from "@/components/inputs/AsyncButton.vue"; export default { name: 'Item', components: { AsyncButton, - EditItem, Modal, InputPhoto, AuthenticatedImage, InputString, InputCombo, AsyncLoader, ClipboardButton, Timeline }, data() { diff --git a/web/src/views/Items.vue b/web/src/views/Items.vue index 21b11d1..30e553f 100644 --- a/web/src/views/Items.vue +++ b/web/src/views/Items.vue @@ -67,11 +67,10 @@