Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add file sort for last created annotations #903

Merged
merged 14 commits into from
Sep 5, 2024
42 changes: 42 additions & 0 deletions app/Http/Controllers/Api/VolumeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,46 @@ public function clone(CloneVolume $request)
->with('messageType', 'success');

}

/**
* Return file ids which are sorted by annotations.created_at
*
* @param int $id
* @return object
* @api {get} volumes{/id}/files/annotation-timestamps Get file ids sorted by recently annotated
* @apiGroup Volumes
mzur marked this conversation as resolved.
Show resolved Hide resolved
* @apiName VolumeSortByAnnotated
* @apiPermission projectMember
*
* @apiParam {Number} id The volume ID.
*
* @apiSuccessExample {json} Success response:
* {
* 1: 1,
* 2: 2,
* 3: 3,
* }
*
*/
public function getFileIdsSortedByAnnotationTimestamps($id)
{
$volume = Volume::findOrFail($id);
$this->authorize('access', $volume);

if ($volume->isImageVolume()) {
$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')
->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')
->orderByRaw("created_at desc nulls last");
}

return $ids->pluck('id');
}
}
4 changes: 4 additions & 0 deletions resources/assets/js/volumes/api/volumes.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,9 @@ export default Vue.resource('api/v1/volumes{/id}', {}, {
clone: {
method: 'POST',
url: 'api/v1/volumes{/id}/clone-to{/project_id}'
},
getFileIdsSortedByAnnotationTimestamps:{
method: 'GET',
url: 'api/v1/volumes{/id}/files/annotation-timestamps'
}
});
32 changes: 32 additions & 0 deletions resources/assets/js/volumes/stores/sorters.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import SortComponent from '../components/sortComponent';
import VolumeApi from '../api/volumes';
import {handleErrorResponse} from '../../core/messages/store';

let filenameSorter = {
id: 'filename',
Expand Down Expand Up @@ -99,6 +101,35 @@ let randomSorter = {
},
};

let annotationTime = {
id: 'annotationTime',
types: ['image', 'video'],
component: {
mixins: [SortComponent],
data() {
return {
volumeId: -1,
fileIds: [],
title: 'Sort images by last created annotation',
text: 'Last annotated',
id: 'annotationTime',

};
},
methods: {
getSequence() {
return VolumeApi.getFileIdsSortedByAnnotationTimestamps({'id': this.volumeId})
.then((res) => res.body)
.catch(handleErrorResponse);
},
},
created() {
this.volumeId = biigle.$require('volumes.volumeId');
this.fileIds = biigle.$require('volumes.fileIds');
},
},
};

/**
* Store for the volume image sorters
*/
Expand All @@ -107,4 +138,5 @@ export default [
filenameSorter,
idSorter,
randomSorter,
annotationTime,
];
4 changes: 4 additions & 0 deletions routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@
'volumes/{id}/clone-to/{id2}', 'VolumeController@clone'
);

$router->get(
'volumes/{id}/files/annotation-timestamps', 'VolumeController@getFileIdsSortedByAnnotationTimestamps'
);

$router->resource('volumes', 'VolumeController', [
'only' => ['index', 'show', 'update'],
'parameters' => ['volumes' => 'id'],
Expand Down
120 changes: 120 additions & 0 deletions tests/php/Http/Controllers/Api/VolumeControllerTest.php
mzur marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
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 Biigle\Tests\VideoAnnotationTest;
use Biigle\Tests\VideoTest;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Queue;
Expand Down Expand Up @@ -306,4 +311,119 @@ public function testCloneVolume()
$response->assertStatus(201);
Queue::assertPushed(CloneImagesOrVideos::class);
}

public function testGetImageIdsSortedByAnnotationTimestamps()
{

$volume = $this
->volume([
'created_at' => '2022-11-09 14:37:00',
'updated_at' => '2022-11-09 14:37:00',
])
->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,
]);
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,
]);
$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(3, $content);
$this->assertSame($content[0], $img2->id);
$this->assertSame($content[1], $img->id);
$this->assertSame($content[2], $img3->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,
]);
$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(3, $content);
$this->assertSame($content[0], $video2->id);
$this->assertSame($content[1], $video->id);
$this->assertSame($content[2], $video3->id);
}
}