From 5ed201c3572fc617a23a4ac0165c8c47a90045b5 Mon Sep 17 00:00:00 2001 From: Leane Schlundt Date: Thu, 1 Aug 2024 10:09:30 +0200 Subject: [PATCH 01/14] Add image sort where last annotation timestamp is used --- .../Views/Volumes/VolumeController.php | 10 +++++- resources/assets/js/volumes/stores/sorters.js | 34 +++++++++++++++++++ resources/views/volumes/show.blade.php | 1 + 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Views/Volumes/VolumeController.php b/app/Http/Controllers/Views/Volumes/VolumeController.php index 17266ecea..42c5ea183 100644 --- a/app/Http/Controllers/Views/Volumes/VolumeController.php +++ b/app/Http/Controllers/Views/Volumes/VolumeController.php @@ -107,13 +107,21 @@ public function index(Request $request, $id) $type = $volume->mediaType->name; + $annotations = $volume->files->mapWithKeys(fn ($f) => [$f->id => $f->annotations]); + $lastAnnotationTimestamps = $annotations->mapWithKeys(fn ($as, $i) => [ + $i => $as->isEmpty() ? + Carbon::minValue()->toDateTimeString() : + ($as->map(fn ($a) => $a->created_at->toDateTimeString()))->max() + ]); + return view('volumes.show', compact( 'volume', 'labelTrees', 'projects', 'fileIds', 'thumbUriTemplate', - 'type' + 'type', + 'lastAnnotationTimestamps' )); } diff --git a/resources/assets/js/volumes/stores/sorters.js b/resources/assets/js/volumes/stores/sorters.js index 50478cb0f..d6d918fe8 100644 --- a/resources/assets/js/volumes/stores/sorters.js +++ b/resources/assets/js/volumes/stores/sorters.js @@ -99,6 +99,39 @@ let randomSorter = { }, }; +let annotationTime = { + id: 'annotationTime', + types: ['image', 'video'], + component: { + mixins: [SortComponent], + data() { + return { + timestamps: [], + title: 'Sort images by last created annotation', + text: 'Last annotated', + id: 'annotationTime', + }; + }, + computed: { + sortedAnnotations() { + return Object.entries(this.timestamps).sort(this.compare); + } + }, + methods: { + getSequence() { + let ids = this.sortedAnnotations.map(e => e[0]); + return new Vue.Promise.resolve(ids); + }, + compare(a, b) { + return Date.parse(b[1]) - Date.parse(a[1]); + } + }, + created() { + this.timestamps = biigle.$require('volumes.annotationTimestamps'); + }, + }, +}; + /** * Store for the volume image sorters */ @@ -107,4 +140,5 @@ export default [ filenameSorter, idSorter, randomSorter, + annotationTime, ]; diff --git a/resources/views/volumes/show.blade.php b/resources/views/volumes/show.blade.php index 467b0612d..25f8ca3d8 100644 --- a/resources/views/volumes/show.blade.php +++ b/resources/views/volumes/show.blade.php @@ -8,6 +8,7 @@ biigle.$declare('volumes.type', '{!! $type !!}'); {{-- Add file IDs as array, too, because the ordering is important! --}} biigle.$declare('volumes.fileIds', {!! $fileIds->keys() !!}); + biigle.$declare('volumes.annotationTimestamps', {!! $lastAnnotationTimestamps !!}); biigle.$declare('volumes.fileUuids', {!! $fileIds !!}); biigle.$declare('volumes.thumbUri', '{{ $thumbUriTemplate }}'); @if ($volume->isImageVolume()) From 07da3c9cf9e83aabc36b33cd14eb3e00dcb27ad5 Mon Sep 17 00:00:00 2001 From: Leane Schlundt Date: Mon, 26 Aug 2024 08:35:17 +0200 Subject: [PATCH 02/14] Return timestamps by using api endpoint --- app/Http/Controllers/Api/VolumeController.php | 32 +++++++++++++++++++ .../Views/Volumes/VolumeController.php | 8 ----- resources/assets/js/volumes/api/volumes.js | 4 +++ resources/assets/js/volumes/stores/sorters.js | 24 ++++++++------ resources/views/volumes/show.blade.php | 1 - routes/api.php | 3 ++ 6 files changed, 54 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/Api/VolumeController.php b/app/Http/Controllers/Api/VolumeController.php index 2ced33713..c81c5237f 100644 --- a/app/Http/Controllers/Api/VolumeController.php +++ b/app/Http/Controllers/Api/VolumeController.php @@ -209,4 +209,36 @@ public function clone(CloneVolume $request) ->with('messageType', 'success'); } + + /** + * Return volume's annotation timestamps of newest annotations + * + * @param int $id + * @return object + * @api {get} volumes{/id}/files/annotation-timestamps Get the newest annotation timestamps of the volume + * @apiGroup Volumes + * @apiPermission projectMember + * + * @apiParam {Number} id The volume ID. + * + * @apiSuccessExample {json} Success response: + * { + * 1: "2024-08-23T07:06:31.000000Z", + * 2: "2024-08-23T06:40:29.000000Z", + * 3: "2024-08-23T06:40:35.000000Z", + * } + * + */ + public function getAnnotationTimestamps($id) + { + $volume = Volume::findOrFail($id); + $this->authorize('access', $volume); + + $timestamps = $volume->files + ->flatMap(fn ($file) => $file->annotations) + ->groupBy('file_id') + ->map(fn ($annotations) => $annotations->max('created_at')); + + return $timestamps; + } } diff --git a/app/Http/Controllers/Views/Volumes/VolumeController.php b/app/Http/Controllers/Views/Volumes/VolumeController.php index 42c5ea183..c22d6cb78 100644 --- a/app/Http/Controllers/Views/Volumes/VolumeController.php +++ b/app/Http/Controllers/Views/Volumes/VolumeController.php @@ -107,13 +107,6 @@ public function index(Request $request, $id) $type = $volume->mediaType->name; - $annotations = $volume->files->mapWithKeys(fn ($f) => [$f->id => $f->annotations]); - $lastAnnotationTimestamps = $annotations->mapWithKeys(fn ($as, $i) => [ - $i => $as->isEmpty() ? - Carbon::minValue()->toDateTimeString() : - ($as->map(fn ($a) => $a->created_at->toDateTimeString()))->max() - ]); - return view('volumes.show', compact( 'volume', 'labelTrees', @@ -121,7 +114,6 @@ public function index(Request $request, $id) 'fileIds', 'thumbUriTemplate', 'type', - 'lastAnnotationTimestamps' )); } diff --git a/resources/assets/js/volumes/api/volumes.js b/resources/assets/js/volumes/api/volumes.js index 627c4ada5..09d95ba95 100644 --- a/resources/assets/js/volumes/api/volumes.js +++ b/resources/assets/js/volumes/api/volumes.js @@ -92,5 +92,9 @@ export default Vue.resource('api/v1/volumes{/id}', {}, { clone: { method: 'POST', url: 'api/v1/volumes{/id}/clone-to{/project_id}' + }, + getAnnotationTimestamps:{ + method: 'GET', + url: 'api/v1/volumes{/id}/files/annotation-timestamps' } }); diff --git a/resources/assets/js/volumes/stores/sorters.js b/resources/assets/js/volumes/stores/sorters.js index d6d918fe8..a51e00573 100644 --- a/resources/assets/js/volumes/stores/sorters.js +++ b/resources/assets/js/volumes/stores/sorters.js @@ -1,4 +1,5 @@ import SortComponent from '../components/sortComponent'; +import VolumeApi from '../api/volumes'; let filenameSorter = { id: 'filename', @@ -106,28 +107,33 @@ let annotationTime = { mixins: [SortComponent], data() { return { - timestamps: [], + volumeId: -1, + fileIds: [], title: 'Sort images by last created annotation', text: 'Last annotated', id: 'annotationTime', + }; }, - computed: { - sortedAnnotations() { - return Object.entries(this.timestamps).sort(this.compare); - } - }, methods: { getSequence() { - let ids = this.sortedAnnotations.map(e => e[0]); - return new Vue.Promise.resolve(ids); + return VolumeApi.getAnnotationTimestamps({'id': this.volumeId}) + .then((res) => res.body) + .then((timestamps) => { + let sorted = Object.entries(timestamps).sort(this.compare); + sorted = sorted.map(e => parseInt(e[0])); + let diff = this.fileIds.filter(id => !sorted.includes(id)); + sorted = sorted.concat(diff); + return sorted; + }); }, compare(a, b) { return Date.parse(b[1]) - Date.parse(a[1]); } }, created() { - this.timestamps = biigle.$require('volumes.annotationTimestamps'); + this.volumeId = biigle.$require('volumes.volumeId'); + this.fileIds = biigle.$require('volumes.fileIds'); }, }, }; diff --git a/resources/views/volumes/show.blade.php b/resources/views/volumes/show.blade.php index 25f8ca3d8..467b0612d 100644 --- a/resources/views/volumes/show.blade.php +++ b/resources/views/volumes/show.blade.php @@ -8,7 +8,6 @@ biigle.$declare('volumes.type', '{!! $type !!}'); {{-- Add file IDs as array, too, because the ordering is important! --}} biigle.$declare('volumes.fileIds', {!! $fileIds->keys() !!}); - biigle.$declare('volumes.annotationTimestamps', {!! $lastAnnotationTimestamps !!}); biigle.$declare('volumes.fileUuids', {!! $fileIds !!}); biigle.$declare('volumes.thumbUri', '{{ $thumbUriTemplate }}'); @if ($volume->isImageVolume()) diff --git a/routes/api.php b/routes/api.php index 1c4997bc0..f2427ba95 100644 --- a/routes/api.php +++ b/routes/api.php @@ -282,6 +282,9 @@ 'volumes/{id}/clone-to/{id2}', 'VolumeController@clone' ); +$router->get( + 'volumes/{id}/files/annotation-timestamps', 'VolumeController@getAnnotationTimestamps'); + $router->resource('volumes', 'VolumeController', [ 'only' => ['index', 'show', 'update'], 'parameters' => ['volumes' => 'id'], From d913027abbebd16a5fa5417e3d8c6633f139d21c Mon Sep 17 00:00:00 2001 From: Leane Schlundt Date: Mon, 26 Aug 2024 08:38:38 +0200 Subject: [PATCH 03/14] Add test --- .../Controllers/Api/VolumeControllerTest.php | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/php/Http/Controllers/Api/VolumeControllerTest.php b/tests/php/Http/Controllers/Api/VolumeControllerTest.php index 44c8cdc43..cd8b71e54 100644 --- a/tests/php/Http/Controllers/Api/VolumeControllerTest.php +++ b/tests/php/Http/Controllers/Api/VolumeControllerTest.php @@ -11,6 +11,9 @@ use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Storage; use Queue; +use Biigle\Tests\ImageAnnotationTest; +use Biigle\Tests\UserTest; +use Biigle\Tests\ImageTest; class VolumeControllerTest extends ApiTestCase { @@ -306,4 +309,50 @@ public function testCloneVolume() $response->assertStatus(201); Queue::assertPushed(CloneImagesOrVideos::class); } + + public function testGetAnnotationTimestamps(){ + + $volume = $this + ->volume([ + 'created_at' => '2022-11-09 14:37:00', + 'updated_at' => '2022-11-09 14:37:00', + ]) + ->fresh(); + + $img = ImageTest::create([ + 'filename' => 'test123.jpg', + 'volume_id' => $volume->id, + ]); + ImageAnnotationTest::create([ + 'created_at' => '2023-11-09 06:37:00', + 'image_id' => $img->id, + ]); + ImageAnnotationTest::create([ + 'created_at' => '2020-11-09 06:37:00', + 'image_id' => $img->id, + ]); + $img2 = ImageTest::create([ + 'filename' => 'test321.jpg', + 'volume_id' => $volume->id + ]); + ImageAnnotationTest::create([ + 'created_at' => '2024-11-09 14:37:00', + 'image_id' => $img2->id, + ]); + + $this->be(UserTest::create()); + $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps")->assertForbidden(); + + $this->beGuest(); + $response = $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps"); + + $response->assertSuccessful(); + $content = json_decode($response->getContent(), true); + + $this->assertCount(2, $content); + $this->assertNotNull($content[$img->id]); + $this->assertNotNull($content[$img2->id]); + $this->assertSame($content[$img->id], "2023-11-09T05:37:00.000000Z"); + $this->assertSame($content[$img2->id], "2024-11-09T13:37:00.000000Z"); + } } From d32f8365526d3602b1c58c8ec234b2b11d4eadc9 Mon Sep 17 00:00:00 2001 From: Leane Schlundt Date: Mon, 26 Aug 2024 08:49:38 +0200 Subject: [PATCH 04/14] Fix lint error and apply smaller changes --- app/Http/Controllers/Api/VolumeController.php | 6 +++--- .../Controllers/Views/Volumes/VolumeController.php | 2 +- routes/api.php | 3 ++- .../Http/Controllers/Api/VolumeControllerTest.php | 13 +++++++------ 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/Api/VolumeController.php b/app/Http/Controllers/Api/VolumeController.php index c81c5237f..7fb3917bb 100644 --- a/app/Http/Controllers/Api/VolumeController.php +++ b/app/Http/Controllers/Api/VolumeController.php @@ -223,9 +223,9 @@ public function clone(CloneVolume $request) * * @apiSuccessExample {json} Success response: * { - * 1: "2024-08-23T07:06:31.000000Z", - * 2: "2024-08-23T06:40:29.000000Z", - * 3: "2024-08-23T06:40:35.000000Z", + * 1: "2024-08-23T07:06:31.000000Z", + * 2: "2024-08-23T06:40:29.000000Z", + * 3: "2024-08-23T06:40:35.000000Z", * } * */ diff --git a/app/Http/Controllers/Views/Volumes/VolumeController.php b/app/Http/Controllers/Views/Volumes/VolumeController.php index c22d6cb78..17266ecea 100644 --- a/app/Http/Controllers/Views/Volumes/VolumeController.php +++ b/app/Http/Controllers/Views/Volumes/VolumeController.php @@ -113,7 +113,7 @@ public function index(Request $request, $id) 'projects', 'fileIds', 'thumbUriTemplate', - 'type', + 'type' )); } diff --git a/routes/api.php b/routes/api.php index f2427ba95..62c6adfd3 100644 --- a/routes/api.php +++ b/routes/api.php @@ -283,7 +283,8 @@ ); $router->get( - 'volumes/{id}/files/annotation-timestamps', 'VolumeController@getAnnotationTimestamps'); + 'volumes/{id}/files/annotation-timestamps', 'VolumeController@getAnnotationTimestamps' +); $router->resource('volumes', 'VolumeController', [ 'only' => ['index', 'show', 'update'], diff --git a/tests/php/Http/Controllers/Api/VolumeControllerTest.php b/tests/php/Http/Controllers/Api/VolumeControllerTest.php index cd8b71e54..944e14380 100644 --- a/tests/php/Http/Controllers/Api/VolumeControllerTest.php +++ b/tests/php/Http/Controllers/Api/VolumeControllerTest.php @@ -7,13 +7,13 @@ use Biigle\Jobs\ProcessNewVolumeFiles; use Biigle\MediaType; use Biigle\Role; +use Biigle\Tests\ImageAnnotationTest; +use Biigle\Tests\ImageTest; use Biigle\Tests\ProjectTest; +use Biigle\Tests\UserTest; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Storage; use Queue; -use Biigle\Tests\ImageAnnotationTest; -use Biigle\Tests\UserTest; -use Biigle\Tests\ImageTest; class VolumeControllerTest extends ApiTestCase { @@ -310,7 +310,8 @@ public function testCloneVolume() Queue::assertPushed(CloneImagesOrVideos::class); } - public function testGetAnnotationTimestamps(){ + public function testGetAnnotationTimestamps() + { $volume = $this ->volume([ @@ -342,10 +343,10 @@ public function testGetAnnotationTimestamps(){ $this->be(UserTest::create()); $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps")->assertForbidden(); - + $this->beGuest(); $response = $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps"); - + $response->assertSuccessful(); $content = json_decode($response->getContent(), true); From 3d4b07678155f5ab6c17724aa5f7d2d603ef15e2 Mon Sep 17 00:00:00 2001 From: Leane Schlundt Date: Wed, 4 Sep 2024 09:49:24 +0200 Subject: [PATCH 05/14] Make query more efficent by returning sorted file ids --- app/Http/Controllers/Api/VolumeController.php | 31 ++++++++++++------- resources/assets/js/volumes/api/volumes.js | 2 +- resources/assets/js/volumes/stores/sorters.js | 14 ++------- routes/api.php | 2 +- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/app/Http/Controllers/Api/VolumeController.php b/app/Http/Controllers/Api/VolumeController.php index 7fb3917bb..b453a1b23 100644 --- a/app/Http/Controllers/Api/VolumeController.php +++ b/app/Http/Controllers/Api/VolumeController.php @@ -211,11 +211,11 @@ public function clone(CloneVolume $request) } /** - * Return volume's annotation timestamps of newest annotations + * Return file ids which are sorted by annotations.created_at * * @param int $id * @return object - * @api {get} volumes{/id}/files/annotation-timestamps Get the newest annotation timestamps of the volume + * @api {get} volumes{/id}/files/annotation-timestamps Get sorted file ids of volume * @apiGroup Volumes * @apiPermission projectMember * @@ -223,22 +223,31 @@ public function clone(CloneVolume $request) * * @apiSuccessExample {json} Success response: * { - * 1: "2024-08-23T07:06:31.000000Z", - * 2: "2024-08-23T06:40:29.000000Z", - * 3: "2024-08-23T06:40:35.000000Z", + * 1: 1, + * 2: 2, + * 3: 3, * } * */ - public function getAnnotationTimestamps($id) + public function getFileIdsSortedByAnnotationTimestamps($id) { $volume = Volume::findOrFail($id); $this->authorize('access', $volume); - $timestamps = $volume->files - ->flatMap(fn ($file) => $file->annotations) - ->groupBy('file_id') - ->map(fn ($annotations) => $annotations->max('created_at')); + if ($volume->isImageVolume()) { + $ids = $volume->files() + ->leftJoin('image_annotations', 'images.id', "=", "image_id") + ->groupBy('images.id') + ->selectRaw('images.id, max(created_at) as created_at'); + } else { + $ids = $volume->files() + ->leftJoin('video_annotations', 'videos.id', "=", "video_id") + ->groupBy('videos.id') + ->selectRaw('videos.id, max(created_at) as created_at'); + } - return $timestamps; + return $ids->get() + ->sortByDesc('created_at') + ->pluck('id'); } } diff --git a/resources/assets/js/volumes/api/volumes.js b/resources/assets/js/volumes/api/volumes.js index 09d95ba95..d8ba94b7b 100644 --- a/resources/assets/js/volumes/api/volumes.js +++ b/resources/assets/js/volumes/api/volumes.js @@ -93,7 +93,7 @@ export default Vue.resource('api/v1/volumes{/id}', {}, { method: 'POST', url: 'api/v1/volumes{/id}/clone-to{/project_id}' }, - getAnnotationTimestamps:{ + getFileIdsSortedByAnnotationTimestamps:{ method: 'GET', url: 'api/v1/volumes{/id}/files/annotation-timestamps' } diff --git a/resources/assets/js/volumes/stores/sorters.js b/resources/assets/js/volumes/stores/sorters.js index a51e00573..c6c3b18b0 100644 --- a/resources/assets/js/volumes/stores/sorters.js +++ b/resources/assets/js/volumes/stores/sorters.js @@ -1,5 +1,6 @@ import SortComponent from '../components/sortComponent'; import VolumeApi from '../api/volumes'; +import {handleErrorResponse} from '../../core/messages/store'; let filenameSorter = { id: 'filename', @@ -117,19 +118,10 @@ let annotationTime = { }, methods: { getSequence() { - return VolumeApi.getAnnotationTimestamps({'id': this.volumeId}) + return VolumeApi.getFileIdsSortedByAnnotationTimestamps({'id': this.volumeId}) .then((res) => res.body) - .then((timestamps) => { - let sorted = Object.entries(timestamps).sort(this.compare); - sorted = sorted.map(e => parseInt(e[0])); - let diff = this.fileIds.filter(id => !sorted.includes(id)); - sorted = sorted.concat(diff); - return sorted; - }); + .catch(handleErrorResponse); }, - compare(a, b) { - return Date.parse(b[1]) - Date.parse(a[1]); - } }, created() { this.volumeId = biigle.$require('volumes.volumeId'); diff --git a/routes/api.php b/routes/api.php index 62c6adfd3..a9e7f2199 100644 --- a/routes/api.php +++ b/routes/api.php @@ -283,7 +283,7 @@ ); $router->get( - 'volumes/{id}/files/annotation-timestamps', 'VolumeController@getAnnotationTimestamps' + 'volumes/{id}/files/annotation-timestamps', 'VolumeController@getFileIdsSortedByAnnotationTimestamps' ); $router->resource('volumes', 'VolumeController', [ From cad49aceb5e4e8959397244af7d717b074eed165 Mon Sep 17 00:00:00 2001 From: Leane Schlundt Date: Wed, 4 Sep 2024 10:06:28 +0200 Subject: [PATCH 06/14] Update test --- tests/php/Http/Controllers/Api/VolumeControllerTest.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/php/Http/Controllers/Api/VolumeControllerTest.php b/tests/php/Http/Controllers/Api/VolumeControllerTest.php index 944e14380..5966d9e10 100644 --- a/tests/php/Http/Controllers/Api/VolumeControllerTest.php +++ b/tests/php/Http/Controllers/Api/VolumeControllerTest.php @@ -310,7 +310,7 @@ public function testCloneVolume() Queue::assertPushed(CloneImagesOrVideos::class); } - public function testGetAnnotationTimestamps() + public function testGetFileIdsSortedByAnnotationTimestamps() { $volume = $this @@ -351,9 +351,7 @@ public function testGetAnnotationTimestamps() $content = json_decode($response->getContent(), true); $this->assertCount(2, $content); - $this->assertNotNull($content[$img->id]); - $this->assertNotNull($content[$img2->id]); - $this->assertSame($content[$img->id], "2023-11-09T05:37:00.000000Z"); - $this->assertSame($content[$img2->id], "2024-11-09T13:37:00.000000Z"); + $this->assertSame($content[0], $img2->id); + $this->assertSame($content[1], $img->id); } } From b77a3cee5c30714b3b4993c957ff6a971c009fde Mon Sep 17 00:00:00 2001 From: Leane Schlundt Date: Thu, 5 Sep 2024 07:39:38 +0200 Subject: [PATCH 07/14] Use full attribute names --- app/Http/Controllers/Api/VolumeController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/VolumeController.php b/app/Http/Controllers/Api/VolumeController.php index b453a1b23..4ca974c7e 100644 --- a/app/Http/Controllers/Api/VolumeController.php +++ b/app/Http/Controllers/Api/VolumeController.php @@ -236,14 +236,14 @@ public function getFileIdsSortedByAnnotationTimestamps($id) if ($volume->isImageVolume()) { $ids = $volume->files() - ->leftJoin('image_annotations', 'images.id', "=", "image_id") + ->leftJoin('image_annotations', 'images.id', "=", "image_annotations.image_id") ->groupBy('images.id') - ->selectRaw('images.id, max(created_at) as created_at'); + ->selectRaw('images.id, max(image_annotations.created_at) as created_at'); } else { $ids = $volume->files() - ->leftJoin('video_annotations', 'videos.id', "=", "video_id") + ->leftJoin('video_annotations', 'videos.id', "=", "video_annotations.video_id") ->groupBy('videos.id') - ->selectRaw('videos.id, max(created_at) as created_at'); + ->selectRaw('videos.id, max(video_annotations.created_at) as created_at'); } return $ids->get() From 5b017c119a2cff8c70ab253cd63ed840b7d3e081 Mon Sep 17 00:00:00 2001 From: Leane Schlundt Date: Thu, 5 Sep 2024 08:09:17 +0200 Subject: [PATCH 08/14] Sort elements in database --- app/Http/Controllers/Api/VolumeController.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/VolumeController.php b/app/Http/Controllers/Api/VolumeController.php index 4ca974c7e..2c7dee9d5 100644 --- a/app/Http/Controllers/Api/VolumeController.php +++ b/app/Http/Controllers/Api/VolumeController.php @@ -238,16 +238,16 @@ public function getFileIdsSortedByAnnotationTimestamps($id) $ids = $volume->files() ->leftJoin('image_annotations', 'images.id', "=", "image_annotations.image_id") ->groupBy('images.id') - ->selectRaw('images.id, max(image_annotations.created_at) as created_at'); + ->selectRaw('images.id, max(image_annotations.created_at) as created_at') + ->orderByRaw("created_at desc nulls last"); } else { $ids = $volume->files() ->leftJoin('video_annotations', 'videos.id', "=", "video_annotations.video_id") ->groupBy('videos.id') - ->selectRaw('videos.id, max(video_annotations.created_at) as created_at'); + ->selectRaw('videos.id, max(video_annotations.created_at) as created_at') + ->orderByRaw("created_at desc nulls last"); } - return $ids->get() - ->sortByDesc('created_at') - ->pluck('id'); + return $ids->pluck('id'); } } From 240f161e73c87b8fa1e42c7dc47355c64f40b3ce Mon Sep 17 00:00:00 2001 From: Leane Schlundt Date: Thu, 5 Sep 2024 08:12:23 +0200 Subject: [PATCH 09/14] Add test case for image volumes --- .../Http/Controllers/Api/VolumeControllerTest.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/php/Http/Controllers/Api/VolumeControllerTest.php b/tests/php/Http/Controllers/Api/VolumeControllerTest.php index 5966d9e10..668fc7729 100644 --- a/tests/php/Http/Controllers/Api/VolumeControllerTest.php +++ b/tests/php/Http/Controllers/Api/VolumeControllerTest.php @@ -320,6 +320,17 @@ public function testGetFileIdsSortedByAnnotationTimestamps() ]) ->fresh(); + $this->be(UserTest::create()); + $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps")->assertForbidden(); + + $this->beGuest(); + $response = $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps"); + + $response->assertSuccessful(); + $content = json_decode($response->getContent(), true); + + $this->assertCount(0, $content); + $img = ImageTest::create([ 'filename' => 'test123.jpg', 'volume_id' => $volume->id, @@ -341,10 +352,6 @@ public function testGetFileIdsSortedByAnnotationTimestamps() 'image_id' => $img2->id, ]); - $this->be(UserTest::create()); - $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps")->assertForbidden(); - - $this->beGuest(); $response = $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps"); $response->assertSuccessful(); From f23d4111d224c81bc284376dbf9dbf0f924483aa Mon Sep 17 00:00:00 2001 From: Leane Schlundt Date: Thu, 5 Sep 2024 08:17:36 +0200 Subject: [PATCH 10/14] Add tests for videos --- .../Controllers/Api/VolumeControllerTest.php | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/tests/php/Http/Controllers/Api/VolumeControllerTest.php b/tests/php/Http/Controllers/Api/VolumeControllerTest.php index 668fc7729..61a7e609b 100644 --- a/tests/php/Http/Controllers/Api/VolumeControllerTest.php +++ b/tests/php/Http/Controllers/Api/VolumeControllerTest.php @@ -11,6 +11,8 @@ use Biigle\Tests\ImageTest; use Biigle\Tests\ProjectTest; use Biigle\Tests\UserTest; +use Biigle\Tests\VideoAnnotationTest; +use Biigle\Tests\VideoTest; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Storage; use Queue; @@ -310,7 +312,7 @@ public function testCloneVolume() Queue::assertPushed(CloneImagesOrVideos::class); } - public function testGetFileIdsSortedByAnnotationTimestamps() + public function testGetImageIdsSortedByAnnotationTimestamps() { $volume = $this @@ -361,4 +363,57 @@ public function testGetFileIdsSortedByAnnotationTimestamps() $this->assertSame($content[0], $img2->id); $this->assertSame($content[1], $img->id); } + + public function testGetVideoIdsSortedByAnnotationTimestamps() + { + + $volume = $this + ->volume([ + 'created_at' => '2022-11-09 14:37:00', + 'updated_at' => '2022-11-09 14:37:00', + 'media_type_id' => MediaType::videoId() + ]) + ->fresh(); + + $this->be(UserTest::create()); + $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps")->assertForbidden(); + + $this->beGuest(); + $response = $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps"); + + $response->assertSuccessful(); + $content = json_decode($response->getContent(), true); + + $this->assertCount(0, $content); + + $video = VideoTest::create([ + 'filename' => 'test123.jpg', + 'volume_id' => $volume->id, + ]); + VideoAnnotationTest::create([ + 'created_at' => '2023-11-09 06:37:00', + 'video_id' => $video->id, + ]); + VideoAnnotationTest::create([ + 'created_at' => '2020-11-09 06:37:00', + 'video_id' => $video->id, + ]); + $video2 = VideoTest::create([ + 'filename' => 'test321.jpg', + 'volume_id' => $volume->id + ]); + VideoAnnotationTest::create([ + 'created_at' => '2024-11-09 14:37:00', + 'video_id' => $video2->id, + ]); + + $response = $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps"); + + $response->assertSuccessful(); + $content = json_decode($response->getContent(), true); + + $this->assertCount(2, $content); + $this->assertSame($content[0], $video2->id); + $this->assertSame($content[1], $video->id); + } } From 7b586f151d296cb3de804e7a9f3e9394eebd2b97 Mon Sep 17 00:00:00 2001 From: Leane Schlundt Date: Thu, 5 Sep 2024 08:32:18 +0200 Subject: [PATCH 11/14] Add test with volume containing a single file --- .../Controllers/Api/VolumeControllerTest.php | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/tests/php/Http/Controllers/Api/VolumeControllerTest.php b/tests/php/Http/Controllers/Api/VolumeControllerTest.php index 61a7e609b..418f6f4a1 100644 --- a/tests/php/Http/Controllers/Api/VolumeControllerTest.php +++ b/tests/php/Http/Controllers/Api/VolumeControllerTest.php @@ -326,17 +326,20 @@ public function testGetImageIdsSortedByAnnotationTimestamps() $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps")->assertForbidden(); $this->beGuest(); + + $img = ImageTest::create([ + 'filename' => 'test123.jpg', + 'volume_id' => $volume->id, + ]); + $response = $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps"); $response->assertSuccessful(); $content = json_decode($response->getContent(), true); - $this->assertCount(0, $content); + $this->assertCount(1, $content); + $this->assertSame($content[0], $img->id); - $img = ImageTest::create([ - 'filename' => 'test123.jpg', - 'volume_id' => $volume->id, - ]); ImageAnnotationTest::create([ 'created_at' => '2023-11-09 06:37:00', 'image_id' => $img->id, @@ -379,17 +382,20 @@ public function testGetVideoIdsSortedByAnnotationTimestamps() $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps")->assertForbidden(); $this->beGuest(); + + $video = VideoTest::create([ + 'filename' => 'test123.jpg', + 'volume_id' => $volume->id, + ]); + $response = $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps"); $response->assertSuccessful(); $content = json_decode($response->getContent(), true); - $this->assertCount(0, $content); + $this->assertCount(1, $content); + $this->assertSame($content[0], $video->id); - $video = VideoTest::create([ - 'filename' => 'test123.jpg', - 'volume_id' => $volume->id, - ]); VideoAnnotationTest::create([ 'created_at' => '2023-11-09 06:37:00', 'video_id' => $video->id, From 34dd7ae8ebcff41caa078bfacb5e3132968575a5 Mon Sep 17 00:00:00 2001 From: Leane Schlundt Date: Thu, 5 Sep 2024 08:36:30 +0200 Subject: [PATCH 12/14] Revert "Add test with volume containing a single file" This reverts commit 7b586f151d296cb3de804e7a9f3e9394eebd2b97. --- .../Controllers/Api/VolumeControllerTest.php | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/tests/php/Http/Controllers/Api/VolumeControllerTest.php b/tests/php/Http/Controllers/Api/VolumeControllerTest.php index 418f6f4a1..61a7e609b 100644 --- a/tests/php/Http/Controllers/Api/VolumeControllerTest.php +++ b/tests/php/Http/Controllers/Api/VolumeControllerTest.php @@ -326,20 +326,17 @@ public function testGetImageIdsSortedByAnnotationTimestamps() $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps")->assertForbidden(); $this->beGuest(); - - $img = ImageTest::create([ - 'filename' => 'test123.jpg', - 'volume_id' => $volume->id, - ]); - $response = $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps"); $response->assertSuccessful(); $content = json_decode($response->getContent(), true); - $this->assertCount(1, $content); - $this->assertSame($content[0], $img->id); + $this->assertCount(0, $content); + $img = ImageTest::create([ + 'filename' => 'test123.jpg', + 'volume_id' => $volume->id, + ]); ImageAnnotationTest::create([ 'created_at' => '2023-11-09 06:37:00', 'image_id' => $img->id, @@ -382,20 +379,17 @@ public function testGetVideoIdsSortedByAnnotationTimestamps() $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps")->assertForbidden(); $this->beGuest(); - - $video = VideoTest::create([ - 'filename' => 'test123.jpg', - 'volume_id' => $volume->id, - ]); - $response = $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps"); $response->assertSuccessful(); $content = json_decode($response->getContent(), true); - $this->assertCount(1, $content); - $this->assertSame($content[0], $video->id); + $this->assertCount(0, $content); + $video = VideoTest::create([ + 'filename' => 'test123.jpg', + 'volume_id' => $volume->id, + ]); VideoAnnotationTest::create([ 'created_at' => '2023-11-09 06:37:00', 'video_id' => $video->id, From 66f3e36eeefd0d4cf77efeef10d3f6ee01b4f3fb Mon Sep 17 00:00:00 2001 From: Leane Schlundt Date: Thu, 5 Sep 2024 08:44:17 +0200 Subject: [PATCH 13/14] Add file without annotations to test --- .../Http/Controllers/Api/VolumeControllerTest.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/php/Http/Controllers/Api/VolumeControllerTest.php b/tests/php/Http/Controllers/Api/VolumeControllerTest.php index 61a7e609b..2ebfaffbc 100644 --- a/tests/php/Http/Controllers/Api/VolumeControllerTest.php +++ b/tests/php/Http/Controllers/Api/VolumeControllerTest.php @@ -353,15 +353,20 @@ public function testGetImageIdsSortedByAnnotationTimestamps() 'created_at' => '2024-11-09 14:37:00', 'image_id' => $img2->id, ]); + $img3 = ImageTest::create([ + 'filename' => 'test321321.jpg', + 'volume_id' => $volume->id + ]); $response = $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps"); $response->assertSuccessful(); $content = json_decode($response->getContent(), true); - $this->assertCount(2, $content); + $this->assertCount(3, $content); $this->assertSame($content[0], $img2->id); $this->assertSame($content[1], $img->id); + $this->assertSame($content[2], $img3->id); } public function testGetVideoIdsSortedByAnnotationTimestamps() @@ -406,14 +411,19 @@ public function testGetVideoIdsSortedByAnnotationTimestamps() 'created_at' => '2024-11-09 14:37:00', 'video_id' => $video2->id, ]); + $video3 = VideoTest::create([ + 'filename' => 'test321123.jpg', + 'volume_id' => $volume->id + ]); $response = $this->getJson("/api/v1/volumes/{$volume->id}/files/annotation-timestamps"); $response->assertSuccessful(); $content = json_decode($response->getContent(), true); - $this->assertCount(2, $content); + $this->assertCount(3, $content); $this->assertSame($content[0], $video2->id); $this->assertSame($content[1], $video->id); + $this->assertSame($content[2], $video3->id); } } From aad44801b73755a55ee8c11d38b1a0c818565704 Mon Sep 17 00:00:00 2001 From: Martin Zurowietz Date: Thu, 5 Sep 2024 09:24:29 +0200 Subject: [PATCH 14/14] Fix apidoc --- app/Http/Controllers/Api/VolumeController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/VolumeController.php b/app/Http/Controllers/Api/VolumeController.php index 2c7dee9d5..2eb86c822 100644 --- a/app/Http/Controllers/Api/VolumeController.php +++ b/app/Http/Controllers/Api/VolumeController.php @@ -215,8 +215,9 @@ public function clone(CloneVolume $request) * * @param int $id * @return object - * @api {get} volumes{/id}/files/annotation-timestamps Get sorted file ids of volume + * @api {get} volumes{/id}/files/annotation-timestamps Get file ids sorted by recently annotated * @apiGroup Volumes + * @apiName VolumeSortByAnnotated * @apiPermission projectMember * * @apiParam {Number} id The volume ID.